Merge "Fix license texts." am: 76908de5ca am: 17c50b7b1c am: f21ab58e57 am: efdcda607c
Original change: https://android-review.googlesource.com/c/platform/cts/+/1955897
Change-Id: Ibe948888c8637ddb85d5596b88305e5b85a5a9ec
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index cfc6773..7f01127 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,6 +1,7 @@
[Builtin Hooks]
clang_format = true
xmllint = true
+bpfmt = true
[Builtin Hooks Options]
clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
@@ -10,46 +11,6 @@
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
- -fw apps/CtsVerifier/
- apps/CtsVerifierUSBCompanion/
- common/device-side/bedstead/
- common/device-side/util/
- hostsidetests/car/
- hostsidetests/devicepolicy
- hostsidetests/dumpsys
- hostsidetests/graphics
- hostsidetests/inputmethodservice/
- hostsidetests/multiuser/
- hostsidetests/scopedstorage/
- hostsidetests/stagedinstall/
- hostsidetests/userspacereboot/
- libs/
- tests/app/
- tests/autofillservice/
- tests/contentcaptureservice/
- tests/devicepolicy/
- tests/inputmethod/
- tests/tests/animation/
- tests/tests/carrierapi/
- tests/tests/content/
- tests/tests/graphics/
- tests/tests/hardware/
- tests/tests/packageinstaller/atomicinstall/
- tests/tests/permission2/
- tests/tests/permission/
- tests/tests/preference/
- tests/tests/print/
- tests/tests/telephony/
- tests/tests/telephony2/
- tests/tests/telephony3/
- tests/tests/telephony4/
- tests/tests/telephonyprovider/
- tests/tests/text/
- tests/tests/theme/
- tests/tests/transition/
- tests/tests/uirendering/
- tests/tests/view/
- tests/tests/widget/
ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
diff --git a/apps/CameraITS/build/envsetup.sh b/apps/CameraITS/build/envsetup.sh
index c52e57e..02ef57c 100644
--- a/apps/CameraITS/build/envsetup.sh
+++ b/apps/CameraITS/build/envsetup.sh
@@ -61,7 +61,7 @@
-for M in sensor_fusion_utils camera_properties_utils capture_request_utils opencv_processing_utils image_processing_utils its_session_utils scene_change_utils target_exposure_utils
+for M in sensor_fusion_utils camera_properties_utils capture_request_utils opencv_processing_utils image_processing_utils its_session_utils target_exposure_utils
do
python "utils/$M.py" 2>&1 | grep -q "OK" || \
echo ">> Unit test for $M failed" >&2
diff --git a/apps/CameraITS/config.yml b/apps/CameraITS/config.yml
index fc712b3..2a668c0 100644
--- a/apps/CameraITS/config.yml
+++ b/apps/CameraITS/config.yml
@@ -28,7 +28,8 @@
brightness: 96
chart_distance: 31.0
debug_mode: "False" # quotes are needed here
- chart_loc_arg: ""
+ lighting_cntl: <controller-type> # can be arduino or "None"
+ lighting_ch: <controller-channel>
camera: <camera-id>
scene: <scene-name> # if <scene-name> left as-is runs all scenes
diff --git a/apps/CameraITS/tests/its_base_test.py b/apps/CameraITS/tests/its_base_test.py
index afa0128..87ba7a8 100644
--- a/apps/CameraITS/tests/its_base_test.py
+++ b/apps/CameraITS/tests/its_base_test.py
@@ -22,6 +22,7 @@
from mobly.controllers import android_device
import its_session_utils
+import lighting_control_utils
ADAPTIVE_BRIGHTNESS_OFF = '0'
TABLET_CMD_DELAY_SEC = 0.5 # found empirically
@@ -37,8 +38,7 @@
NOT_YET_MANDATED = {
'scene0': [['test_test_patterns', 30],
['test_tonemap_curve', 30]],
- 'scene1_1': [['test_ae_precapture_trigger', 28],
- ['test_channel_saturation', 29]],
+ 'scene1_1': [['test_ae_precapture_trigger', 28]],
'scene1_2': [],
'scene2_a': [['test_jpeg_quality', 30]],
'scene2_b': [['test_auto_per_frame_control', NOT_YET_MANDATED_ALL]],
@@ -50,7 +50,6 @@
'scene5': [],
'scene6': [['test_zoom', 30]],
'sensor_fusion': [],
- 'scene_change': [['test_scene_change', 31]]
}
@@ -77,10 +76,13 @@
if self.user_params.get('chart_distance'):
self.chart_distance = float(self.user_params['chart_distance'])
logging.debug('Chart distance: %s cm', self.chart_distance)
- if self.user_params.get('chart_loc_arg'):
- self.chart_loc_arg = self.user_params['chart_loc_arg']
+ if (self.user_params.get('lighting_cntl') and
+ self.user_params.get('lighting_ch')):
+ self.lighting_cntl = self.user_params['lighting_cntl']
+ self.lighting_ch = str(self.user_params['lighting_ch'])
else:
- self.chart_loc_arg = ''
+ self.lighting_cntl = 'None'
+ self.lighting_ch = '1'
if self.user_params.get('debug_mode'):
self.debug_mode = True if self.user_params[
'debug_mode'] == 'True' else False
@@ -115,6 +117,15 @@
self._setup_devices(num_devices)
+ arduino_serial_port = lighting_control_utils.lighting_control(
+ self.lighting_cntl, self.lighting_ch)
+ if arduino_serial_port:
+ lighting_control_utils.set_light_brightness(
+ self.lighting_ch, 255, arduino_serial_port)
+ logging.debug('Light is turned ON.')
+ else:
+ logging.info('Ensure lights ON')
+
def _setup_devices(self, num):
"""Sets up each device in parallel if more than one device."""
if num not in VALID_NUM_DEVICES:
@@ -132,6 +143,7 @@
def setup_dut(self, device):
self.dut.adb.shell(
'am start -n com.android.cts.verifier/.CtsVerifierActivity')
+ logging.debug('Setting up device: %s', str(device))
# Wait for the app screen to appear.
time.sleep(WAIT_TIME_SEC)
@@ -171,17 +183,18 @@
logging.debug('dumpsys window output: %s', output.decode('utf-8').strip())
output_list = str(output.decode('utf-8')).strip().split(' ')
for val in output_list:
- if 'LandscapeRotation' in val:
- landscape_val = str(val.split('=')[-1])
- # For some tablets the values are in constant forms such as ROTATION_90
- if 'ROTATION_90' in landscape_val:
- landscape_val = '1'
- logging.debug('Changing the orientation to landscape mode.')
- self.tablet.adb.shell(['settings', 'put', 'system', 'user_rotation',
- landscape_val])
- break
- logging.debug('Reported tablet orientation is: %d',
- int(self.tablet.adb.shell('settings get system user_rotation')))
+ if 'LandscapeRotation' in val:
+ landscape_val = str(val.split('=')[-1])
+ # For some tablets the values are in constant forms such as ROTATION_90
+ if 'ROTATION_90' in landscape_val:
+ landscape_val = '1'
+ logging.debug('Changing the orientation to landscape mode.')
+ self.tablet.adb.shell(['settings', 'put', 'system', 'user_rotation',
+ landscape_val])
+ break
+ logging.debug(
+ 'Reported tablet orientation is: %d',
+ int(self.tablet.adb.shell('settings get system user_rotation')))
def parse_hidden_camera_id(self):
"""Parse the string of camera ID into an array.
diff --git a/apps/CameraITS/tests/scene0/test_metadata.py b/apps/CameraITS/tests/scene0/test_metadata.py
index b0995a7..c6a69a3 100644
--- a/apps/CameraITS/tests/scene0/test_metadata.py
+++ b/apps/CameraITS/tests/scene0/test_metadata.py
@@ -23,6 +23,8 @@
import capture_request_utils
import its_session_utils
+_HYPERFOCAL_MIN = 0.02
+
class MetadataTest(its_base_test.ItsBaseTest):
"""Test the validity of some metadata entries.
@@ -119,7 +121,8 @@
check(self, props['android.sensor.blackLevelPattern'] is not None,
'props["android.sensor.blackLevelPattern"] is not None')
- assert not self.failed
+ if self.failed:
+ raise AssertionError('props failure. Check test_log.DEBUG.')
if not camera_properties_utils.legacy(props):
# Test: pixel_pitch, FOV, and hyperfocal distance are reasonable
@@ -132,23 +135,27 @@
pixel_pitch_w = (sensor_size['width'] / fmts[0]['width'] * 1E3)
logging.debug('Assert pixel_pitch WxH: %.2f um, %.2f um', pixel_pitch_w,
pixel_pitch_h)
- assert 0.7 <= pixel_pitch_w <= 10
- assert 0.7 <= pixel_pitch_h <= 10
- assert 0.333 <= pixel_pitch_w/pixel_pitch_h <= 3.0
+ if (not 0.7 <= pixel_pitch_w <= 10 or
+ not 0.7 <= pixel_pitch_h <= 10 or
+ not 0.333 <= pixel_pitch_w/pixel_pitch_h <= 3.0):
+ raise AssertionError(
+ f'Pixel pitch error! w: {pixel_pitch_w}, h: {pixel_pitch_h}')
diag = math.sqrt(sensor_size['height']**2 + sensor_size['width']**2)
fl = md['android.lens.focalLength']
logging.debug('Focal length: %.3f', fl)
fov = 2 * math.degrees(math.atan(diag / (2 * fl)))
logging.debug('Assert field of view: %.1f degrees', fov)
- assert 10 <= fov <= 130
+ if not 10 <= fov <= 130:
+ raise AssertionError(f'FoV error: {fov:.1f}')
if camera_properties_utils.lens_approx_calibrated(props):
diopter_hyperfocal = props['android.lens.info.hyperfocalDistance']
if diopter_hyperfocal != 0.0:
hyperfocal = 1.0 / diopter_hyperfocal
- logging.debug('Assert hyperfocal distance: %.2f m', hyperfocal)
- assert 0.02 <= hyperfocal
+ if _HYPERFOCAL_MIN > hyperfocal:
+ raise AssertionError('hyperfocal distance error: '
+ f'{hyperfocal:.2f}, MIN: {_HYPERFOCAL_MIN}')
logging.debug('Minimum focus distance: %3.f',
props['android.lens.info.minimumFocusDistance'])
diff --git a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
index 863ffbc..b5f672d 100644
--- a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
+++ b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
@@ -46,7 +46,8 @@
sens_step = (sens_range[1] - sens_range[0]) // NUM_STEPS
sens_list = range(sens_range[0], sens_range[1], sens_step)
exp = min(props['android.sensor.info.exposureTimeRange'])
- assert exp != 0
+ if exp == 0:
+ raise AssertionError('Minimum exposure time is 0')
reqs = [
capture_request_utils.manual_capture_request(s, exp)
for s in sens_list
@@ -56,11 +57,10 @@
caps = cam.do_capture(reqs, fmt)
for i, cap in enumerate(caps):
s_req = sens_list[i]
- s_res = cap['metadata']['android.sensor.sensitivity']
- msg = 's_write: %d, s_read: %d, TOL: %.2f' % (s_req, s_res,
- ERROR_TOLERANCE)
- assert s_req >= s_res, msg
- assert s_res / float(s_req) > ERROR_TOLERANCE, msg
+ s_cap = cap['metadata']['android.sensor.sensitivity']
+ if (s_req < s_cap or s_cap / float(s_req) < ERROR_TOLERANCE):
+ raise AssertionError(f's_request: {s_req}, s_capture: {s_cap}, '
+ f'TOL: {ERROR_TOLERANCE:.2f}')
if __name__ == '__main__':
diff --git a/apps/CameraITS/tests/scene0/test_read_write.py b/apps/CameraITS/tests/scene0/test_read_write.py
index e8b514a..32bcd56 100644
--- a/apps/CameraITS/tests/scene0/test_read_write.py
+++ b/apps/CameraITS/tests/scene0/test_read_write.py
@@ -56,7 +56,8 @@
logging.debug('sensor sensitivity range: %s', sens_range)
# determine if exposure test range is within sensor reported range
- assert sensor_exp_range[0] != 0
+ if sensor_exp_range[0] == 0:
+ raise AssertionError('Min expsoure == 0')
exp_range = []
if sensor_exp_range[0] < TEST_EXP_RANGE[0]:
exp_range.append(TEST_EXP_RANGE[0])
@@ -135,8 +136,11 @@
logging.debug('e_write: %d, e_read: %d, RTOL: %.2f',
fail['e_write'], fail['e_read'], RTOL_EXP_GAIN)
- # assert PASS/FAIL
- assert not e_failed + s_failed
+ # PASS/FAIL
+ if e_failed:
+ raise AssertionError(f'Exposure fails: {e_failed}')
+ if s_failed:
+ raise AssertionError(f'Sensitivity fails: {s_failed}')
if __name__ == '__main__':
diff --git a/apps/CameraITS/tests/scene0/test_sensor_events.py b/apps/CameraITS/tests/scene0/test_sensor_events.py
index e8afbb2..f5c165f 100644
--- a/apps/CameraITS/tests/scene0/test_sensor_events.py
+++ b/apps/CameraITS/tests/scene0/test_sensor_events.py
@@ -51,9 +51,9 @@
for key, existing in sensors.items():
# Vibrator does not return any sensor event. b/142653973
if existing and key != 'vibrator':
- e_msg = 'Sensor %s has no events!' % key
# Check len(events[key]) > 0
- assert events[key], e_msg
+ if not events[key]:
+ raise AssertionError(f'Sensor {key} has no events!')
if __name__ == '__main__':
diff --git a/apps/CameraITS/tests/scene0/test_test_patterns.py b/apps/CameraITS/tests/scene0/test_test_patterns.py
index dbf1ee2..6aced4e 100644
--- a/apps/CameraITS/tests/scene0/test_test_patterns.py
+++ b/apps/CameraITS/tests/scene0/test_test_patterns.py
@@ -153,10 +153,11 @@
True)
# Check pattern for correctness
- assert check_pattern(cap, props, pattern)
+ if not check_pattern(cap, props, pattern):
+ raise AssertionError(f'Pattern {pattern} failed')
else:
logging.debug('%d not in android.sensor.availableTestPatternModes.',
- (pattern))
+ pattern)
class TestPatterns(its_base_test.ItsBaseTest):
diff --git a/apps/CameraITS/tests/scene0/test_unified_timestamps.py b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
index f7e5991..a4c83ad 100644
--- a/apps/CameraITS/tests/scene0/test_unified_timestamps.py
+++ b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
@@ -63,7 +63,8 @@
for sensor, existing in sensors.items():
# Vibrator doesn't generate outputs: b/142653973
if existing and sensor != 'vibrator':
- assert events[sensor], '%s sensor has no events!' % sensor
+ if not events[sensor]:
+ raise AssertionError(f'{sensor} has no events!')
ts_sensor_first[sensor] = events[sensor][0]['time']
ts_sensor_last[sensor] = events[sensor][-1]['time']
@@ -78,8 +79,12 @@
if existing and sensor != 'vibrator':
logging.debug('%s timestamps: %d %d', sensor, ts_sensor_first[sensor],
ts_sensor_last[sensor])
- assert ts_image0 < ts_sensor_first[sensor] < ts_image1
- assert ts_image0 < ts_sensor_last[sensor] < ts_image1
+ if (not ts_image0 < ts_sensor_first[sensor] < ts_image1 or
+ not ts_image0 < ts_sensor_last[sensor] < ts_image1):
+ raise AssertionError(
+ f'{sensor} times not bounded by camera! camera: '
+ f'{ts_image0}:{ts_image1}, {sensor}: '
+ f'{ts_sensor_first[sensor]}:{ts_sensor_last[sensor]}')
if __name__ == '__main__':
diff --git a/apps/CameraITS/tests/scene0/test_vibration_restriction.py b/apps/CameraITS/tests/scene0/test_vibration_restriction.py
index 45c82e9..0ece4b0 100644
--- a/apps/CameraITS/tests/scene0/test_vibration_restriction.py
+++ b/apps/CameraITS/tests/scene0/test_vibration_restriction.py
@@ -102,10 +102,10 @@
'Accel variance with/without/restricted vibration (%f, %f, %f)',
var_w_vibration, var_wo_vibration, var_w_vibration_restricted)
- e_msg = 'Device vibrated while vibration is muted'
vibration_variance = var_w_vibration_restricted < (
var_wo_vibration * THRESHOLD_VIBRATION_VAR)
- assert vibration_variance, e_msg
+ if not vibration_variance:
+ raise AssertionError('Device vibrated while vibration is muted.')
if __name__ == '__main__':
diff --git a/apps/CameraITS/tests/scene1_1/test_3a.py b/apps/CameraITS/tests/scene1_1/test_3a.py
index 9db3bdc..9100e79 100644
--- a/apps/CameraITS/tests/scene1_1/test_3a.py
+++ b/apps/CameraITS/tests/scene1_1/test_3a.py
@@ -28,6 +28,11 @@
NAME = os.path.splitext(os.path.basename(__file__))[0]
+def assert_is_number(x):
+ if np.isnan(x):
+ raise AssertionError(f'{x} is not a number!')
+
+
class ThreeATest(its_base_test.ItsBaseTest):
"""Test basic camera 3A behavior.
@@ -57,15 +62,22 @@
logging.debug('AE: sensitivity %d, exposure %dns', s, e)
logging.debug('AF: distance %.3f', focus)
- assert len(awb_gains) == AWB_GAINS_LENGTH
+ if len(awb_gains) != AWB_GAINS_LENGTH:
+ raise AssertionError(
+ f'AWB gains has unexpected # of terms! {awb_gains}')
for g in awb_gains:
- assert not np.isnan(g)
- assert len(awb_xform) == AWB_XFORM_LENGTH
+ assert_is_number(g)
+ if len(awb_xform) != AWB_XFORM_LENGTH:
+ raise AssertionError(
+ f'AWB transform has unexpected # of terms! {awb_xform}')
for x in awb_xform:
- assert not np.isnan(x)
- assert s > 0
- assert e > 0
- assert focus >= 0
+ assert_is_number(x)
+ if s <= 0:
+ raise AssertionError(f'sensitivity {s} <= 0!')
+ if e <= 0:
+ raise AssertionError(f'exposure {e} <= 0!')
+ if focus < 0:
+ raise AssertionError(f'focus distance {focus} < 0!')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py b/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py
index dae3c05..49886a5 100644
--- a/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py
+++ b/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py
@@ -86,8 +86,8 @@
state = cap['metadata']['android.control.aeState']
msg = 'AE state after manual request %d: %d' % (i, state)
logging.debug('%s', msg)
- e_msg = msg + ' AE_INACTIVE: %d' % AE_INACTIVE
- assert state == AE_INACTIVE, e_msg
+ if state != AE_INACTIVE:
+ raise AssertionError(f'{msg} AE_INACTIVE: {AE_INACTIVE}')
# Capture auto request and verify the AE state: no trigger.
logging.debug('Auto capture')
@@ -97,9 +97,9 @@
state = cap['metadata']['android.control.aeState']
msg = 'AE state after auto request: %d' % state
logging.debug('%s', msg)
- e_msg = msg + ' AE_SEARCHING: %d, AE_CONVERGED: %d' % (
- AE_SEARCHING, AE_CONVERGED)
- assert state in [AE_SEARCHING, AE_CONVERGED], e_msg
+ if state not in [AE_SEARCHING, AE_CONVERGED]:
+ raise AssertionError(f'{msg} AE_SEARCHING: {AE_SEARCHING}, '
+ f'AE_CONVERGED: {AE_CONVERGED}')
# Capture auto request with a precapture trigger.
logging.debug('Auto capture with precapture trigger')
@@ -108,9 +108,10 @@
state = cap['metadata']['android.control.aeState']
msg = 'AE state after auto request with precapture trigger: %d' % state
logging.debug('%s', msg)
- e_msg = msg + ' AE_SEARCHING: %d, AE_CONVERGED: %d, AE_PRECAPTURE: %d' % (
- AE_SEARCHING, AE_CONVERGED, AE_PRECAPTURE)
- assert state in [AE_SEARCHING, AE_CONVERGED, AE_PRECAPTURE], e_msg
+ if state not in [AE_SEARCHING, AE_CONVERGED, AE_PRECAPTURE]:
+ raise AssertionError(f'{msg} AE_SEARCHING: {AE_SEARCHING}, '
+ f'AE_CONVERGED: {AE_CONVERGED}, '
+ f'AE_PRECAPTURE: {AE_PRECAPTURE}')
# Capture some more auto requests, and AE should converge.
logging.debug('Additional auto captures')
@@ -122,8 +123,8 @@
logging.debug('%s', msg)
if state == AE_CONVERGED:
return
- e_msg = msg + ' AE_CONVERGED: %d' % AE_CONVERGED
- assert state == AE_CONVERGED, e_msg
+ if state != AE_CONVERGED:
+ raise AssertionError(f'{msg} AE_CONVERGED: {AE_CONVERGED}')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py b/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py
index 72c791d..2cf6247 100644
--- a/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py
+++ b/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py
@@ -123,22 +123,22 @@
# Check AWB gains & transform in manual results match values from do_3a
for g, x in [(awb_gains_m1, awb_xform_m1), (awb_gains_m2, awb_xform_m2)]:
- e_msg = 'awb_xform 3A: %s, manual: %s, ATOL=%.2f' % (
- str(awb_xform), str(x), AWB_MANUAL_ATOL)
- assert np.allclose(awb_xform, x, atol=AWB_MANUAL_ATOL, rtol=0), e_msg
- e_msg = 'awb_gains 3A: %s, manual: %s, ATOL=%.2f' % (
- str(awb_gains), str(g), AWB_MANUAL_ATOL)
- assert np.allclose(awb_gains, g, atol=AWB_MANUAL_ATOL, rtol=0), e_msg
+ if not np.allclose(awb_xform, x, atol=AWB_MANUAL_ATOL, rtol=0):
+ raise AssertionError(
+ f'awb_xform 3A: {awb_xform}, manual: {x}, ATOL={AWB_MANUAL_ATOL}')
+ if not np.allclose(awb_gains, g, atol=AWB_MANUAL_ATOL, rtol=0):
+ raise AssertionError(
+ f'awb_gains 3A: {awb_gains}, manual: {g}, ATOL={AWB_MANUAL_ATOL}')
# Check AWB gains & transform in auto results match values from do_3a
- e_msg = 'awb_xform 3A: %s, auto: %s, RTOL=%.2f, ATOL=%.2f' % (
- str(awb_xform), str(awb_xform_a), AWB_AUTO_RTOL, AWB_AUTO_ATOL)
- assert np.allclose(awb_xform_a, awb_xform, atol=AWB_AUTO_ATOL,
- rtol=AWB_AUTO_RTOL), e_msg
- e_msg = 'awb_gains 3A: %s, auto: %s, RTOL=%.2f, ATOL=%.2f' % (
- str(awb_gains), str(awb_gains_a), AWB_AUTO_RTOL, AWB_AUTO_ATOL)
- assert np.allclose(awb_gains_a, awb_gains, atol=AWB_AUTO_ATOL,
- rtol=AWB_AUTO_RTOL), e_msg
+ if not np.allclose(awb_xform_a, awb_xform, atol=AWB_AUTO_ATOL,
+ rtol=AWB_AUTO_RTOL):
+ raise AssertionError(f'awb_xform 3A: {awb_xform}, auto: {awb_xform_a},'
+ f'RTOL={AWB_AUTO_RTOL}, ATOL={AWB_AUTO_ATOL}')
+ if not np.allclose(awb_gains_a, awb_gains, atol=AWB_AUTO_ATOL,
+ rtol=AWB_AUTO_RTOL):
+ raise AssertionError(f'awb_gains 3A: {awb_gains}, auto: {awb_gains_a},'
+ f'RTOL={AWB_AUTO_RTOL}, ATOL={AWB_AUTO_ATOL}')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_black_white.py b/apps/CameraITS/tests/scene1_1/test_black_white.py
index 2421cc9..88ff723 100644
--- a/apps/CameraITS/tests/scene1_1/test_black_white.py
+++ b/apps/CameraITS/tests/scene1_1/test_black_white.py
@@ -15,6 +15,7 @@
import logging
+import math
import os.path
import matplotlib
from matplotlib import pylab
@@ -29,6 +30,7 @@
import image_processing_utils
import its_session_utils
+_ANDROID10_API_LEVEL = 29
CH_FULL_SCALE = 255
CH_THRESH_BLACK = 6
CH_THRESH_WHITE = CH_FULL_SCALE - 6
@@ -137,21 +139,23 @@
# Assert blacks below CH_THRESH_BLACK
for ch, mean in enumerate(black_means):
- e_msg = '%s black: %.1f, THRESH: %.f' % (
- COLOR_PLANES[ch], mean, CH_THRESH_BLACK)
- assert mean < CH_THRESH_BLACK, e_msg
+ if mean >= CH_THRESH_BLACK:
+ raise AssertionError(f'{COLOR_PLANES[ch]} black: {mean:.1f}, '
+ f'THRESH: {CH_THRESH_BLACK}')
# Assert whites above CH_THRESH_WHITE
for ch, mean in enumerate(white_means):
- e_msg = '%s white: %.1f, THRESH: %.f' % (
- COLOR_PLANES[ch], mean, CH_THRESH_WHITE)
- assert mean > CH_THRESH_WHITE, e_msg
+ if mean <= CH_THRESH_WHITE:
+ raise AssertionError(f'{COLOR_PLANES[ch]} white: {mean:.1f}, '
+ f'THRESH: {CH_THRESH_WHITE}')
# Assert channels saturate evenly (was test_channel_saturation)
- e_msg = 'ch saturation not equal! RGB: %s, ATOL: %.f' % (
- str(white_means), CH_TOL_WHITE)
- assert np.isclose(
- np.amin(white_means), np.amax(white_means), atol=CH_TOL_WHITE), e_msg
+ first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
+ if first_api_level >= _ANDROID10_API_LEVEL:
+ if not math.isclose(
+ np.amin(white_means), np.amax(white_means), abs_tol=CH_TOL_WHITE):
+ raise AssertionError('channel saturation not equal! '
+ f'RGB: {white_means}, ATOL: {CH_TOL_WHITE}')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py b/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py
index b43067c..8354cf1 100644
--- a/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py
+++ b/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py
@@ -128,11 +128,10 @@
# PASS/FAIL based on center patch similarity.
for plane, means in enumerate([r_means, g_means, b_means]):
spread = max(means) - min(means)
- msg = '%s spread: %.5f, spread_thresh: %.2f' % (
- COLORS[plane], spread, spread_thresh)
- logging.debug('%s', msg)
- assert spread < spread_thresh, msg
+ logging.debug('%s spread: %.5f', COLORS[plane], spread)
+ if spread > spread_thresh:
+ raise AssertionError(f'{COLORS[plane]} spread > THRESH. spread: '
+ f'{spread}, THRESH: {spread_thresh:.2f}')
if __name__ == '__main__':
test_runner.main()
-
diff --git a/apps/CameraITS/tests/scene1_1/test_capture_result.py b/apps/CameraITS/tests/scene1_1/test_capture_result.py
index 73dfe5a..304a737 100644
--- a/apps/CameraITS/tests/scene1_1/test_capture_result.py
+++ b/apps/CameraITS/tests/scene1_1/test_capture_result.py
@@ -83,8 +83,10 @@
logging.debug('AWB region: %s', str(metadata['android.control.awbRegions']))
# Color correction gains and transform should be the same size
- assert len(awb_gains) == AWB_GAINS_NUM
- assert len(awb_xform) == AWB_XFORM_NUM
+ if len(awb_gains) != AWB_GAINS_NUM:
+ raise AssertionError(f'AWB gains wrong length! {awb_gains}')
+ if len(awb_xform) != AWB_XFORM_NUM:
+ raise AssertionError(f'AWB transform wrong length! {awb_xform}')
def test_auto(cam, props, log_path):
@@ -110,23 +112,33 @@
ctrl_mode = metadata['android.control.mode']
logging.debug('Control mode: %d', ctrl_mode)
- assert ctrl_mode == 1, 'ctrl_mode: %d' % ctrl_mode
+ if ctrl_mode != 1:
+ raise AssertionError(f'ctrl_mode != 1: {ctrl_mode}')
# Color correction gain and transform must be valid.
metadata_checks(metadata, props)
awb_gains = metadata['android.colorCorrection.gains']
awb_xform = metadata['android.colorCorrection.transform']
- assert all([g > 0 for g in awb_gains])
- assert all([t['denominator'] != 0 for t in awb_xform])
+ if not all([g > 0 for g in awb_gains]):
+ raise AssertionError(f'AWB gains has negative terms: {awb_gains}')
+ if not all([t['denominator'] != 0 for t in awb_xform]):
+ raise AssertionError(f'AWB transform has 0 denominators: {awb_xform}')
# Color correction should not match the manual settings.
- assert not np.allclose(awb_gains, MANUAL_AWB_GAINS, atol=ISCLOSE_ATOL)
- assert not all([is_close_rational(awb_xform[i], MANUAL_AWB_XFORM[i])
- for i in range(AWB_XFORM_NUM)])
+ if np.allclose(awb_gains, MANUAL_AWB_GAINS, atol=ISCLOSE_ATOL):
+ raise AssertionError('Manual and automatic AWB gains are same! '
+ f'manual: {MANUAL_AWB_GAINS}, auto: {awb_gains}, '
+ f'ATOL: {ISCLOSE_ATOL}')
+ if all([is_close_rational(awb_xform[i], MANUAL_AWB_XFORM[i])
+ for i in range(AWB_XFORM_NUM)]):
+ raise AssertionError('Manual and automatic AWB transforms are same! '
+ f'manual: {MANUAL_AWB_XFORM}, auto: {awb_xform}, '
+ f'ATOL: {ISCLOSE_ATOL}')
# Exposure time must be valid.
exp_time = metadata['android.sensor.exposureTime']
- assert exp_time > 0
+ if exp_time <= 0:
+ raise AssertionError(f'exposure time is <= 0! {exp_time}')
# Draw lens shading correction map
lsc_obj = metadata['android.statistics.lensShadingCorrectionMap']
@@ -173,35 +185,47 @@
ctrl_mode = metadata['android.control.mode']
logging.debug('Control mode: %d', ctrl_mode)
- assert ctrl_mode == 0, 'ctrl_mode: %d' % ctrl_mode
+ if ctrl_mode != 0:
+ raise AssertionError(f'ctrl_mode: {ctrl_mode}')
# Color correction gains and transform should be the same size and
# values as the manually set values.
metadata_checks(metadata, props)
awb_gains = metadata['android.colorCorrection.gains']
awb_xform = metadata['android.colorCorrection.transform']
- assert (all([np.isclose(awb_gains[i], MANUAL_GAINS_OK[0][i],
+ if not (all([np.isclose(awb_gains[i], MANUAL_GAINS_OK[0][i],
atol=ISCLOSE_ATOL) for i in range(AWB_GAINS_NUM)]) or
all([np.isclose(awb_gains[i], MANUAL_GAINS_OK[1][i],
atol=ISCLOSE_ATOL) for i in range(AWB_GAINS_NUM)]) or
all([np.isclose(awb_gains[i], MANUAL_GAINS_OK[2][i],
- atol=ISCLOSE_ATOL) for i in range(AWB_GAINS_NUM)]))
- assert (all([is_close_rational(awb_xform[i], MANUAL_AWB_XFORM[i])
- for i in range(AWB_XFORM_NUM)]))
+ atol=ISCLOSE_ATOL) for i in range(AWB_GAINS_NUM)])):
+ raise AssertionError('request/capture mismatch in AWB gains! '
+ f'req: {MANUAL_GAINS_OK}, cap: {awb_gains}, '
+ f'ATOL: {ISCLOSE_ATOL}')
+ if not (all([is_close_rational(awb_xform[i], MANUAL_AWB_XFORM[i])
+ for i in range(AWB_XFORM_NUM)])):
+ raise AssertionError('request/capture mismatch in AWB transforms! '
+ f'req: {MANUAL_AWB_XFORM}, cap: {awb_xform}, '
+ f'ATOL: {ISCLOSE_ATOL}')
# The returned tonemap must be linear.
curves = [metadata['android.tonemap.curve']['red'],
metadata['android.tonemap.curve']['green'],
metadata['android.tonemap.curve']['blue']]
logging.debug('Tonemap: %s', str(curves[0][1::16]))
- for c in curves:
- assert c, 'c in curves is empty.'
- assert all([np.isclose(c[i], c[i+1], atol=ISCLOSE_ATOL)
- for i in range(0, len(c), 2)])
+ for j, c in enumerate(curves):
+ if not c:
+ raise AssertionError('c in curves is empty.')
+ if not all([np.isclose(c[i], c[i+1], atol=ISCLOSE_ATOL)
+ for i in range(0, len(c), 2)]):
+ raise AssertionError(f"tonemap 'RGB'[i] is not linear! {c}")
# Exposure time must be close to the requested exposure time.
exp_time = metadata['android.sensor.exposureTime']
- assert np.isclose(exp_time*1.0E-6, exp_min*1.0E-6, atol=ISCLOSE_ATOL)
+ if not np.isclose(exp_time, exp_min, atol=ISCLOSE_ATOL/1E-06):
+ raise AssertionError('request/capture exposure time mismatch! '
+ f'req: {exp_min}, cap: {exp_time}, '
+ f'ATOL: {ISCLOSE_ATOL/1E-6}')
# Lens shading map must be valid
lsc_obj = metadata['android.statistics.lensShadingCorrectionMap']
@@ -209,9 +233,11 @@
lsc_map_w = lsc_obj['width']
lsc_map_h = lsc_obj['height']
logging.debug('LSC map: %dx%d, %s', lsc_map_w, lsc_map_h, str(lsc_map[:8]))
- assert (lsc_map_w > 0 and lsc_map_h > 0 and
- lsc_map_w*lsc_map_h*4 == len(lsc_map))
- assert all([m >= 1 for m in lsc_map])
+ if not (lsc_map_w > 0 and lsc_map_h > 0 and
+ lsc_map_w*lsc_map_h*4 == len(lsc_map)):
+ raise AssertionError(f'Incorrect lens shading map size! {lsc_map}')
+ if not all([m >= 1 for m in lsc_map]):
+ raise AssertionError(f'Lens shading map has negative vals! {lsc_map}')
# Draw lens shading correction map
draw_lsc_plot(lsc_map_w, lsc_map_h, lsc_map, 'manual', log_path)
diff --git a/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py b/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py
index 69cc33f..8a598bd 100644
--- a/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py
+++ b/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py
@@ -71,7 +71,8 @@
# Calculate a center crop region.
zoom = min(3.0, camera_properties_utils.get_max_digital_zoom(props))
- assert zoom >= 1, 'zoom: %.2f' % zoom
+ if zoom < 1:
+ raise AssertionError(f'zoom: {zoom:.2f}')
crop_w = aw // zoom
crop_h = ah // zoom
@@ -129,13 +130,13 @@
ex = aw * err_delta
ey = ah * err_delta
logging.debug('error X, Y: %.2f, %.2f', ex, ey)
- e_msg = 'expected: %s, reported: %s, ex: %.2f, ex: %.2f' % (
- str(cr_expected), str(cr_reported), ex, ey)
- assert (
+ if not (
(abs(cr_expected['left'] - cr_reported['left']) <= ex) and
(abs(cr_expected['right'] - cr_reported['right']) <= ex) and
(abs(cr_expected['top'] - cr_reported['top']) <= ey) and
- (abs(cr_expected['bottom'] - cr_reported['bottom']) <= ey)), e_msg
+ (abs(cr_expected['bottom'] - cr_reported['bottom']) <= ey)):
+ raise AssertionError(f'expected: {cr_expected}, reported: '
+ f'{cr_reported}, ex: {ex:.2f}, ey: {ey:.2f}')
# Also check the image content; 3 of the 4 shots should match.
# Note that all the shots are RGB below; the variable names correspond
@@ -178,10 +179,12 @@
logging.debug('YUV diff (crop vs. non-crop): %.3f', diff_yuv)
logging.debug('RAW diff (crop vs. non-crop): %.3f', diff_raw)
- assert diff_yuv > DIFF_THRESH, 'diff_yuv: %.3f, THRESH: %.2f' % (
- diff_yuv, DIFF_THRESH)
- assert diff_raw < DIFF_THRESH, 'diff_raw: %.3f, THRESH: %.2f' % (
- diff_raw, DIFF_THRESH)
+ if diff_yuv <= DIFF_THRESH:
+ raise AssertionError('YUV diff too small! '
+ f'diff_yuv: {diff_yuv:.3f}, THRESH: {DIFF_THRESH}')
+ if diff_raw >= DIFF_THRESH:
+ raise AssertionError('RAW diff too big! '
+ f'diff_raw: {diff_raw:.3f}, THRESH: {DIFF_THRESH}')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_crop_regions.py b/apps/CameraITS/tests/scene1_1/test_crop_regions.py
index 77e35ed..8e979a3 100644
--- a/apps/CameraITS/tests/scene1_1/test_crop_regions.py
+++ b/apps/CameraITS/tests/scene1_1/test_crop_regions.py
@@ -68,9 +68,9 @@
# Uses a 2x digital zoom.
max_digital_zoom = capture_request_utils.get_max_digital_zoom(props)
- e_msg = 'Max digital zoom: %d, THRESH: %d' % (max_digital_zoom,
- MIN_DIGITAL_ZOOM_THRESH)
- assert max_digital_zoom >= MIN_DIGITAL_ZOOM_THRESH, e_msg
+ if max_digital_zoom < MIN_DIGITAL_ZOOM_THRESH:
+ raise AssertionError(f'Max digital zoom: {max_digital_zoom}, '
+ f'THRESH: {MIN_DIGITAL_ZOOM_THRESH}')
# Capture a full frame.
req = capture_request_utils.manual_capture_request(s, e)
@@ -95,6 +95,7 @@
reqs.append(req)
caps_regions = cam.do_capture(reqs)
match_failed = False
+ e_msg = []
for i, cap in enumerate(caps_regions):
a = cap['metadata']['android.scaler.cropRegion']
ax, ay = a['left'], a['top']
@@ -125,11 +126,13 @@
min_diff_region = j
if i != min_diff_region:
match_failed = True
+ e_msg.append(f'i != min_diff_region. i: {i}, '
+ f'min_diff_region: {min_diff_region}. ')
logging.debug('Crop image %d (%d,%d %dx%d) best match with region %d',
i, ax, ay, aw, ah, min_diff_region)
- assert not match_failed
+ if match_failed:
+ raise AssertionError(f'Match failed: {e_msg}')
if __name__ == '__main__':
test_runner.main()
-
diff --git a/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py b/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py
index 8af5aed..3589586 100644
--- a/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py
+++ b/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py
@@ -103,7 +103,9 @@
# Test each raw color channel (R, GR, GB, B)
noise_profile = cap['metadata']['android.sensor.noiseProfile']
- assert len(noise_profile) == len(BAYER_LIST)
+ if len(noise_profile) != len(BAYER_LIST):
+ raise AssertionError(
+ f'noise_profile wrong length! {len(noise_profile)}')
for i, ch in enumerate(BAYER_LIST):
# Get the noise model parameters for this channel of this shot.
s, o = noise_profile[cfa_idxs[i]]
@@ -129,11 +131,11 @@
# so the check remains correct even after the signal starts to clip.
mean_minus_3sigma = mean_img_ch - math.sqrt(var_model) * 3
if mean_minus_3sigma < 0:
- e_msg = 'Pixel distribution crosses 0. Likely black level '
- e_msg += 'over-clips. Linear model is not valid. '
- e_msg += 'mean: %.3e, var: %.3e, u-3s: %.3e' % (
- mean_img_ch, var_model, mean_minus_3sigma)
- assert mean_minus_3sigma < 0, e_msg
+ if mean_minus_3sigma >= 0:
+ raise AssertionError(
+ 'Pixel distribution crosses 0. Likely black level over-clips.'
+ f' Linear model is not valid. mean: {mean_img_ch:.3e},'
+ f' var: {var_model:.3e}, u-3s: {mean_minus_3sigma:.3e}')
else:
var = image_processing_utils.compute_image_variances(patch_norm)[0]
var_meas[i].append(var)
@@ -167,7 +169,8 @@
logging.debug('%s variance diffs: %s', ch, str(var_diffs))
for j, diff in enumerate(var_diffs):
thresh = max(VAR_ATOL_THRESH, VAR_RTOL_THRESH*var_exp[i][j])
- assert diff <= thresh, 'var diff: %.5f, thresh: %.4f' % (diff, thresh)
+ if diff > thresh:
+ raise AssertionError(f'var diff: {diff:.5f}, thresh: {thresh:.4f}')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_jpeg.py b/apps/CameraITS/tests/scene1_1/test_jpeg.py
index 2c11d29..cc16a05 100644
--- a/apps/CameraITS/tests/scene1_1/test_jpeg.py
+++ b/apps/CameraITS/tests/scene1_1/test_jpeg.py
@@ -100,9 +100,9 @@
rms_diff = image_processing_utils.compute_image_rms_difference(
rgb_means_yuv, rgb_means_jpg)
logging.debug('RMS difference: %.3f', rms_diff)
- e_msg = 'RMS difference: %.3f, spec: %.2f' % (
- rms_diff, THRESHOLD_MAX_RMS_DIFF)
- assert rms_diff < THRESHOLD_MAX_RMS_DIFF, e_msg
+ if rms_diff >= THRESHOLD_MAX_RMS_DIFF:
+ raise AssertionError(
+ f'RMS diff: {rms_diff:.3f}, spec: {THRESHOLD_MAX_RMS_DIFF}')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_latching.py b/apps/CameraITS/tests/scene1_1/test_latching.py
index 158bb0f..6ebb405 100644
--- a/apps/CameraITS/tests/scene1_1/test_latching.py
+++ b/apps/CameraITS/tests/scene1_1/test_latching.py
@@ -90,7 +90,7 @@
elif req_type == 'iso':
reqs.append(iso_mult_req)
else:
- assert 0, 'Incorrect capture request!'
+ raise AssertionError(f'Incorrect capture request! {req_type}')
caps = cam.do_capture(reqs, fmt)
for i, cap in enumerate(caps):
@@ -121,8 +121,8 @@
# check G mean pattern for correctness
g_avg_for_caps = sum(g_means) / len(g_means)
g_high = [g / g_avg_for_caps > 1 for g in g_means]
- assert g_high == PATTERN_CHECK, 'G means: %s, TEMPLATE: %s' % (
- str(g_means), str(REQ_PATTERN))
+ if g_high != PATTERN_CHECK:
+ raise AssertionError(f'G means: {g_means}, TEMPLATE: {REQ_PATTERN}')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_linearity.py b/apps/CameraITS/tests/scene1_1/test_linearity.py
index 885311b..33baa52 100644
--- a/apps/CameraITS/tests/scene1_1/test_linearity.py
+++ b/apps/CameraITS/tests/scene1_1/test_linearity.py
@@ -126,9 +126,11 @@
range(len(sensitivities)), means, 1, full=True)
logging.debug('Line: m=%f, b=%f, resid=%f',
line[0], line[1], residuals[0])
- msg = 'residual: %.5f, THRESH: %.4f' % (residuals[0], RESIDUAL_THRESH)
- assert residuals[0] < RESIDUAL_THRESH, msg
- assert line[0] > 0, 'slope %.6f less than 0!' % line[0]
+ if residuals[0] > RESIDUAL_THRESH:
+ raise AssertionError(
+ 'residual: {residuals[0]:.5f}, THRESH: {RESIDUAL_THRESH}')
+ if line[0] <= 0:
+ raise AssertionError(f'slope {line[0]:.6f} <= 0!')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_locked_burst.py b/apps/CameraITS/tests/scene1_1/test_locked_burst.py
index db72259..c586373 100644
--- a/apps/CameraITS/tests/scene1_1/test_locked_burst.py
+++ b/apps/CameraITS/tests/scene1_1/test_locked_burst.py
@@ -102,15 +102,15 @@
logging.debug('%s patch mean spread %.5f. means = %s',
plane, spread, str(means))
for j in range(BURST_LEN):
- e_msg = '%s frame %d too dark! mean: %.5f, THRESH: %.2f' % (
- plane, j, min_means, VALUE_THRESH)
- assert min_means > VALUE_THRESH, e_msg
+ if min_means <= VALUE_THRESH:
+ raise AssertionError(f'{plane} frame {j} too dark! mean: '
+ f'{min_means:.5f}, THRESH: {VALUE_THRESH}')
threshold = SPREAD_THRESH
if camera_properties_utils.manual_sensor(props):
threshold = SPREAD_THRESH_MANUAL_SENSOR
- e_msg = '%s center patch spread: %.5f, THRESH: %.2f' % (
- plane, spread, threshold)
- assert spread < threshold, e_msg
+ if spread >= threshold:
+ raise AssertionError(f'{plane} center patch spread: {spread:.5f}, '
+ f'THRESH: {threshold:.2f}')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_multi_camera_match.py b/apps/CameraITS/tests/scene1_1/test_multi_camera_match.py
deleted file mode 100644
index 6c8d0e8..0000000
--- a/apps/CameraITS/tests/scene1_1/test_multi_camera_match.py
+++ /dev/null
@@ -1,144 +0,0 @@
-# Copyright 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Verifies sub-cameras have similar RGB values for gray patch."""
-
-
-import logging
-import os.path
-
-from mobly import test_runner
-import numpy as np
-
-import its_base_test
-import camera_properties_utils
-import capture_request_utils
-import image_processing_utils
-import its_session_utils
-
-_NAME = os.path.splitext(os.path.basename(__file__))[0]
-_PATCH_H = 0.0625 # 1/16 x 1/16 in center of image
-_PATCH_W = 0.0625
-_PATCH_X = 0.5 - _PATCH_W/2
-_PATCH_Y = 0.5 - _PATCH_H/2
-_THRESH_DIFF = 0.06
-_THRESH_GAIN = 0.1
-_THRESH_EXP = 0.05
-
-
-class MultiCameraMatchTest(its_base_test.ItsBaseTest):
- """Test both cameras give similar RGB values for gray patch.
-
- This test uses android.lens.info.availableFocalLengths to determine
- subcameras. The test will take images of the gray chart for each cameras,
- crop the center patch, and compare the Y (of YUV) means of the two images.
- Y means must be within _THRESH_DIFF for the test to pass.
-
- Cameras that use android.control.zoomRatioRange will have only 1 focal
- length and will need separate test.
- """
-
- def test_multi_camera_match(self):
- logging.debug('Starting %s', _NAME)
- yuv_sizes = {}
- with its_session_utils.ItsSession(
- device_id=self.dut.serial,
- camera_id=self.camera_id,
- hidden_physical_id=self.hidden_physical_id) as cam:
- props = cam.get_camera_properties()
- props = cam.override_with_hidden_physical_camera_props(props)
- log_path = self.log_path
-
- # check SKIP conditions
- camera_properties_utils.skip_unless(
- camera_properties_utils.per_frame_control(props) and
- camera_properties_utils.logical_multi_camera(props))
-
- # Load chart for scene
- its_session_utils.load_scene(
- cam, props, self.scene, self.tablet, self.chart_distance)
-
- ids = camera_properties_utils.logical_multi_camera_physical_ids(props)
- for i in ids:
- physical_props = cam.get_camera_properties_by_id(i)
- camera_properties_utils.skip_unless(
- not camera_properties_utils.mono_camera(physical_props) and
- camera_properties_utils.backward_compatible(physical_props))
- yuv_sizes[i] = capture_request_utils.get_available_output_sizes(
- 'yuv', physical_props)
- if i == ids[0]: # get_available_output_sizes returns sorted list
- yuv_match_sizes = yuv_sizes[i]
- else:
- yuv_match_sizes = list(
- set(yuv_sizes[i]).intersection(yuv_match_sizes))
-
- # find matched size for captures
- yuv_match_sizes.sort()
- w = yuv_match_sizes[-1][0]
- h = yuv_match_sizes[-1][1]
- logging.debug('Matched YUV size: (%d, %d)', w, h)
-
- # do 3a and create requests
- cam.do_3a()
- reqs = []
- avail_fls = sorted(props['android.lens.info.availableFocalLengths'],
- reverse=True)
- # SKIP test if only 1 focal length
- camera_properties_utils.skip_unless(len(avail_fls) > 1)
-
- for i, fl in enumerate(avail_fls):
- reqs.append(capture_request_utils.auto_capture_request())
- reqs[i]['android.lens.focalLength'] = fl
- if i > 0:
- # Calculate the active sensor region for a non-cropped image
- zoom = avail_fls[0] / fl
- aa = props['android.sensor.info.activeArraySize']
- aa_w, aa_h = aa['right'] - aa['left'], aa['bottom'] - aa['top']
-
- # Calculate a center crop region.
- assert zoom >= 1
- crop_w = aa_w // zoom
- crop_h = aa_h // zoom
- crop_region = {'left': aa_w // 2 - crop_w // 2,
- 'top': aa_h // 2 - crop_h // 2,
- 'right': aa_w // 2 + crop_w // 2,
- 'bottom': aa_h // 2 + crop_h // 2}
- reqs[i]['android.scaler.cropRegion'] = crop_region
-
- # capture YUVs
- y_means = {}
- e_msg = ''
- fmt = [{'format': 'yuv', 'width': w, 'height': h}]
- caps = cam.do_capture(reqs, fmt)
- for i, fl in enumerate(avail_fls):
- img = image_processing_utils.convert_capture_to_rgb_image(
- caps[i], props=props)
- image_processing_utils.write_image(img, '%s_yuv_fl=%s.jpg' % (
- os.path.join(log_path, _NAME), fl))
- y, _, _ = image_processing_utils.convert_capture_to_planes(
- caps[i], props=props)
- y_mean = image_processing_utils.compute_image_means(
- image_processing_utils.get_image_patch(
- y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H))[0]
- msg = 'y[%s]: %.3f, ' % (fl, y_mean)
- logging.debug(msg)
- e_msg += msg
- y_means[fl] = y_mean
-
- # compare Y means
- e_msg += 'TOL=%.5f' % _THRESH_DIFF
- assert np.isclose(max(y_means.values()), min(y_means.values()),
- rtol=_THRESH_DIFF), e_msg
-
-if __name__ == '__main__':
- test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py b/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py
index 7000bc2..446003e 100644
--- a/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py
+++ b/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py
@@ -101,8 +101,9 @@
# Assert each shot is brighter than previous.
for ch, means in enumerate([r_means, g_means, b_means]):
for i in range(len(EXP_MULT_FACTORS)-1):
- e_msg = '%s [i+1]: %.4f, [i]: %.4f' % (COLORS[ch], means[i+1], means[i])
- assert means[i+1] > means[i], e_msg
+ if means[i+1] <= means[i]:
+ raise AssertionError(
+ f'{COLORS[ch]} [i+1]: {means[i+1]:.4f}, [i]: {means[i]:.4f}')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py b/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py
index 601b7fb..fca0f7d 100644
--- a/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py
+++ b/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py
@@ -108,19 +108,23 @@
# Assert state behavior
logging.debug('Reported modes: %s', str(modes))
logging.debug('Reported states: %s', str(states))
- assert modes == list(FLASH_MODES.values()), str(modes)
+ if modes != list(FLASH_MODES.values()):
+ raise AssertionError(f'modes != FLASH_MODES! {modes}')
- e_msg = 'flash state reported[OFF]: %d' % states[FLASH_MODES['OFF']]
- assert states[FLASH_MODES['OFF']] not in [
- FLASH_STATES['FIRED'], FLASH_STATES['PARTIAL']], e_msg
+ if states[FLASH_MODES['OFF']] in [
+ FLASH_STATES['FIRED'], FLASH_STATES['PARTIAL']]:
+ raise AssertionError('flash state reported[OFF]: '
+ f"{states[FLASH_MODES['OFF']]}")
- e_msg = 'flash state reported[SINGLE]: %d' % states[FLASH_MODES['SINGLE']]
- assert states[FLASH_MODES['SINGLE']] in [
- FLASH_STATES['FIRED'], FLASH_STATES['PARTIAL']], e_msg
+ if states[FLASH_MODES['SINGLE']] not in [
+ FLASH_STATES['FIRED'], FLASH_STATES['PARTIAL']]:
+ raise AssertionError('flash state reported[SINGLE]: '
+ f"{states[FLASH_MODES['SINGLE']]}")
- e_msg = 'flash state reported[TORCH]: %d' % states[FLASH_MODES['TORCH']]
- assert states[FLASH_MODES['TORCH']] in [
- FLASH_STATES['FIRED'], FLASH_STATES['PARTIAL']], e_msg
+ if states[FLASH_MODES['TORCH']] not in [
+ FLASH_STATES['FIRED'], FLASH_STATES['PARTIAL']]:
+ raise AssertionError('flash state reported[TORCH]: '
+ f"{states[FLASH_MODES['TORCH']]}")
# Assert image behavior: change between OFF & SINGLE
logging.debug('Brightness means: %s', str(means))
@@ -128,23 +132,23 @@
grad_delta = grads[FLASH_MODES['SINGLE']] - grads[FLASH_MODES['OFF']]
mean_delta = ((means[FLASH_MODES['SINGLE']] - means[FLASH_MODES['OFF']]) /
means[FLASH_MODES['OFF']])
- e_msg = 'gradient SINGLE-OFF: %.3f, ATOL: %.3f' % (
- grad_delta, GRADIENT_DELTA)
- e_msg += ' mean SINGLE:OFF %.3f, ATOL: %.3f' % (
- mean_delta, Y_RELATIVE_DELTA_FLASH)
- assert (grad_delta > GRADIENT_DELTA or
- mean_delta > Y_RELATIVE_DELTA_FLASH), e_msg
+ if not (grad_delta > GRADIENT_DELTA or
+ mean_delta > Y_RELATIVE_DELTA_FLASH):
+ raise AssertionError(f'gradient SINGLE-OFF: {grad_delta:.3f}, '
+ f'ATOL: {GRADIENT_DELTA}, '
+ f'mean SINGLE:OFF {mean_delta:.3f}, '
+ f'ATOL: {Y_RELATIVE_DELTA_FLASH}')
# Assert image behavior: change between OFF & TORCH
grad_delta = grads[FLASH_MODES['TORCH']] - grads[FLASH_MODES['OFF']]
mean_delta = ((means[FLASH_MODES['TORCH']] - means[FLASH_MODES['OFF']]) /
means[FLASH_MODES['OFF']])
- e_msg = 'gradient TORCH-OFF: %.3f, ATOL: %.3f' % (
- grad_delta, GRADIENT_DELTA)
- e_msg += ' mean TORCH:OFF %.3f, ATOL: %.3f' % (
- mean_delta, Y_RELATIVE_DELTA_TORCH)
- assert (grad_delta > GRADIENT_DELTA or
- mean_delta > Y_RELATIVE_DELTA_TORCH), e_msg
+ if not (grad_delta > GRADIENT_DELTA or
+ mean_delta > Y_RELATIVE_DELTA_TORCH):
+ raise AssertionError(f'gradient TORCH-OFF: {grad_delta:.3f}, '
+ f'ATOL: {GRADIENT_DELTA}, '
+ f'mean TORCH:OFF {mean_delta:.3f}, '
+ f'ATOL: {Y_RELATIVE_DELTA_TORCH}')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py b/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py
index b605757..6323a4a 100644
--- a/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py
+++ b/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py
@@ -143,58 +143,62 @@
pylab.xticks(NR_MODES_LIST)
matplotlib.pyplot.savefig('%s_plot_SNRs.png' % os.path.join(log_path, NAME))
- assert nr_modes_reported == NR_MODES_LIST
+ if nr_modes_reported != NR_MODES_LIST:
+ raise AssertionError(f'{nr_modes_reported} != {NR_MODES_LIST}')
for j in range(NUM_COLORS):
# Higher SNR is better
# Verify OFF is not better than FAST
- e_msg = '%s OFF: %.3f, FAST: %.3f, TOL: %.3f' % (
- COLORS[j], snrs[j][NR_MODES['OFF']], snrs[j][NR_MODES['FAST']],
- SNR_TOLERANCE)
- assert (snrs[j][NR_MODES['OFF']] < snrs[j][NR_MODES['FAST']] +
- SNR_TOLERANCE), e_msg
+ if (snrs[j][NR_MODES['OFF']] >= snrs[j][NR_MODES['FAST']] +
+ SNR_TOLERANCE):
+ raise AssertionError(
+ f"{COLORS[j]} OFF: {snrs[j][NR_MODES['OFF']]:.3f}, "
+ f"FAST: {snrs[j][NR_MODES['FAST']]:.3f}, TOL: {SNR_TOLERANCE}")
# Verify FAST is not better than HQ
- e_msg = '%s FAST: %.3f, HQ: %.3f, TOL: %.3f' % (
- COLORS[j], snrs[j][NR_MODES['FAST']], snrs[j][NR_MODES['HQ']],
- SNR_TOLERANCE)
- assert (snrs[j][NR_MODES['FAST']] < snrs[j][NR_MODES['HQ']] +
- SNR_TOLERANCE), e_msg
+ if (snrs[j][NR_MODES['FAST']] >= snrs[j][NR_MODES['HQ']] +
+ SNR_TOLERANCE):
+ raise AssertionError(
+ f"{COLORS[j]} FAST: {snrs[j][NR_MODES['FAST']]:.3f}, "
+ f"HQ: {snrs[j][NR_MODES['HQ']]:.3f}, TOL: {SNR_TOLERANCE}")
# Verify HQ is better than OFF
- e_msg = '%s OFF: %.3f, HQ: %.3f' % (
- COLORS[j], snrs[j][NR_MODES['OFF']], snrs[j][NR_MODES['HQ']])
- assert snrs[j][NR_MODES['HQ']] > snrs[j][NR_MODES['OFF']], e_msg
+ if snrs[j][NR_MODES['HQ']] <= snrs[j][NR_MODES['OFF']]:
+ raise AssertionError(
+ f"{COLORS[j]} OFF: {snrs[j][NR_MODES['OFF']]:.3f}, "
+ f"HQ: {snrs[j][NR_MODES['HQ']]:.3f}")
if camera_properties_utils.noise_reduction_mode(props, NR_MODES['MIN']):
# Verify OFF is not better than MINIMAL
- e_msg = '%s OFF: %.3f, MIN: %.3f, TOL: %.3f' % (
- COLORS[j], snrs[j][NR_MODES['OFF']], snrs[j][NR_MODES['MIN']],
- SNR_TOLERANCE)
- assert (snrs[j][NR_MODES['OFF']] < snrs[j][NR_MODES['MIN']] +
- SNR_TOLERANCE), e_msg
+ if not(snrs[j][NR_MODES['OFF']] < snrs[j][NR_MODES['MIN']] +
+ SNR_TOLERANCE):
+ raise AssertionError(
+ f"{COLORS[j]} OFF: {snrs[j][NR_MODES['OFF']]:.3f}, "
+ f"MIN: {snrs[j][NR_MODES['MIN']]:.3f}, TOL: {SNR_TOLERANCE}")
# Verify MINIMAL is not better than HQ
- e_msg = '%s MIN: %.3f, HQ: %.3f, TOL: %.3f' % (
- COLORS[j], snrs[j][NR_MODES['MIN']], snrs[j][NR_MODES['HQ']],
- SNR_TOLERANCE)
- assert (snrs[j][NR_MODES['MIN']] < snrs[j][NR_MODES['HQ']] +
- SNR_TOLERANCE), e_msg
+ if not (snrs[j][NR_MODES['MIN']] < snrs[j][NR_MODES['HQ']] +
+ SNR_TOLERANCE):
+ raise AssertionError(
+ f"{COLORS[j]} MIN: {snrs[j][NR_MODES['MIN']]:.3f}, "
+ f"HQ: {snrs[j][NR_MODES['HQ']]:.3f}, TOL: {SNR_TOLERANCE}")
+ # Verify ZSL is close to MINIMAL
if camera_properties_utils.noise_reduction_mode(props, NR_MODES['ZSL']):
- # Verify ZSL is close to MINIMAL
- e_msg = '%s ZSL: %.3f, MIN: %.3f, TOL: %.3f' % (
- COLORS[j], snrs[j][NR_MODES['ZSL']], snrs[j][NR_MODES['MIN']],
- SNR_TOLERANCE)
- assert np.isclose(snrs[j][NR_MODES['ZSL']], snrs[j][NR_MODES['MIN']],
- atol=SNR_TOLERANCE), e_msg
+ if not np.isclose(snrs[j][NR_MODES['ZSL']], snrs[j][NR_MODES['MIN']],
+ atol=SNR_TOLERANCE):
+ raise AssertionError(
+ f"{COLORS[j]} ZSL: {snrs[j][NR_MODES['ZSL']]:.3f}, "
+ f"MIN: {snrs[j][NR_MODES['MIN']]:.3f}, TOL: {SNR_TOLERANCE}")
+
elif camera_properties_utils.noise_reduction_mode(props, NR_MODES['ZSL']):
# Verify ZSL is close to OFF
- e_msg = '%s OFF: %.3f, ZSL: %.3f, TOL: %.3f' % (
- COLORS[j], snrs[j][NR_MODES['OFF']], snrs[j][NR_MODES['ZSL']],
- SNR_TOLERANCE)
- assert np.isclose(snrs[j][NR_MODES['ZSL']], snrs[j][NR_MODES['OFF']],
- atol=SNR_TOLERANCE), e_msg
+ if not np.isclose(snrs[j][NR_MODES['ZSL']], snrs[j][NR_MODES['OFF']],
+ atol=SNR_TOLERANCE):
+ raise AssertionError(
+ f"{COLORS[j]} OFF: {snrs[j][NR_MODES['OFF']]:3f}, "
+ f"ZSL: {snrs[j][NR_MODES['ZSL']]:3f}, TOL: {SNR_TOLERANCE}")
+
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py b/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py
index 4c6af25..4cc4546 100644
--- a/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py
+++ b/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py
@@ -77,9 +77,9 @@
camera_properties_utils.per_frame_control(props) and
not camera_properties_utils.mono_camera(props))
- # Load chart for scene
+ # Load chart for scene (chart_distance=0 for no chart scaling)
its_session_utils.load_scene(
- cam, props, self.scene, self.tablet, self.chart_distance)
+ cam, props, self.scene, self.tablet, chart_distance=0)
# Find sensitivity range and create capture requests
sens_min, _ = props['android.sensor.info.sensitivityRange']
@@ -130,9 +130,10 @@
# Asserts that each shot is noisier than previous
for i in x[0:-1]:
- e_msg = 'variances [i]: %.5f, [i+1]: %.5f, THRESH: %.2f' % (
- variances[i], variances[i+1], _VAR_THRESH)
- assert variances[i] < variances[i+1] / _VAR_THRESH, e_msg
+ if variances[i] >= variances[i+1] / _VAR_THRESH:
+ raise AssertionError(
+ f'variances [i]: {variances[i] :.5f}, [i+1]: '
+ f'{variances[i+1]:.5f}, THRESH: {_VAR_THRESH}')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py b/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py
index 985cf12..8c542ab 100644
--- a/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py
+++ b/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py
@@ -65,9 +65,9 @@
name_with_log_path = os.path.join(self.log_path, NAME)
camera_fov = float(cam.calc_camera_fov(props))
- # Load chart for scene
+ # Load chart for scene (chart_distance=0 for no chart scaling)
its_session_utils.load_scene(
- cam, props, self.scene, self.tablet, self.chart_distance)
+ cam, props, self.scene, self.tablet, chart_distance=0)
# Expose for the scene with min sensitivity
sens_min, _ = props['android.sensor.info.sensitivityRange']
diff --git a/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py b/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py
index b1f1b8a..214dc04 100644
--- a/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py
+++ b/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py
@@ -52,21 +52,28 @@
out_surface = {'width': size[0], 'height': size[1], 'format': img_type}
cap = cam.do_capture(req, out_surface)
if img_type == 'jpg':
- assert cap['format'] == 'jpeg'
+ if cap['format'] != 'jpeg':
+ raise AssertionError(f"{cap['format']} != jpeg")
img = image_processing_utils.decompress_jpeg_to_rgb_image(cap['data'])
else:
- assert cap['format'] == img_type
+ if cap['format'] != img_type:
+ raise AssertionError(f"{cap['format']} != {img_type}")
img = image_processing_utils.convert_capture_to_rgb_image(cap)
- assert cap['width'] == size[0]
- assert cap['height'] == size[1]
+ if cap['width'] != size[0]:
+ raise AssertionError(f"{cap['width']} != {size[0]}")
+ if cap['height'] != size[1]:
+ raise AssertionError(f"{cap['height']} != {size[1]}")
if debug:
image_processing_utils.write_image(img, '%s_%s_w%d_h%d.jpg'%(
os.path.join(log_path, NAME), img_type, size[0], size[1]))
if img_type == 'jpg':
- assert img.shape[0] == size[1]
- assert img.shape[1] == size[0]
- assert img.shape[2] == 3
+ if img.shape[0] != size[1]:
+ raise AssertionError(f'{img.shape[0]} != {size[1]}')
+ if img.shape[1] != size[0]:
+ raise AssertionError(f'{img.shape[1]} != {size[0]}')
+ if img.shape[2] != 3:
+ raise AssertionError(f'{img.shape[2]} != 3')
patch = image_processing_utils.get_image_patch(
img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
rgb = image_processing_utils.compute_image_means(patch)
@@ -133,8 +140,8 @@
max_diff = max(max_diff, rms_diff)
msg = 'Max RMS difference: %.4f' % max_diff
logging.debug('%s', msg)
- e_msg = msg + ' spec: %.3f' % THRESHOLD_MAX_RMS_DIFF
- assert max_diff < THRESHOLD_MAX_RMS_DIFF, e_msg
+ if max_diff >= THRESHOLD_MAX_RMS_DIFF:
+ raise AssertionError(f'{msg} spec: {THRESHOLD_MAX_RMS_DIFF}')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene2_a/test_auto_flash.py b/apps/CameraITS/tests/scene2_a/test_auto_flash.py
new file mode 100644
index 0000000..c6d0633
--- /dev/null
+++ b/apps/CameraITS/tests/scene2_a/test_auto_flash.py
@@ -0,0 +1,177 @@
+# 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.
+"""Verifies android.flash.mode parameters is applied when set."""
+
+
+import logging
+import os.path
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import lighting_control_utils
+import image_processing_utils
+import its_session_utils
+
+AE_MODES = {0: 'OFF', 1: 'ON', 2: 'ON_AUTO_FLASH', 3: 'ON_ALWAYS_FLASH',
+ 4: 'ON_AUTO_FLASH_REDEYE', 5: 'ON_EXTERNAL_FLASH'}
+AE_STATES = {0: 'INACTIVE', 1: 'SEARCHING', 2: 'CONVERGED', 3: 'LOCKED',
+ 4: 'FLASH_REQUIRED', 5: 'PRECAPTURE'}
+_FLASH_CHECK_FIRST_API_LEVEL = 32
+_GRAD_DELTA_ATOL = 100 # gradiant for tablets as screen aborbs energy
+_MEAN_DELTA_ATOL = 100 # mean used for reflective charts
+_NUM_FRAMES = 8
+_PATCH_H = 0.25 # center 25%
+_PATCH_W = 0.25
+_PATCH_X = 0.5 - _PATCH_W/2
+_PATCH_Y = 0.5 - _PATCH_H/2
+_TEST_NAME = os.path.splitext(os.path.basename(__file__))[0]
+VGA_W, VGA_H = 640, 480
+
+
+def take_captures(cam, auto_flash=False):
+ req = capture_request_utils.auto_capture_request()
+ if auto_flash:
+ req['android.control.aeMode'] = 2 # 'ON_AUTO_FLASH'
+ fmt = {'format': 'yuv', 'width': VGA_W, 'height': VGA_H}
+ return cam.do_capture([req]*_NUM_FRAMES, fmt)
+
+
+class AutoFlashTest(its_base_test.ItsBaseTest):
+ """Test that the android.flash.mode parameter is applied."""
+
+ def test_auto_flash(self):
+ logging.debug('AE_MODES: %s', str(AE_MODES))
+ logging.debug('AE_STATES: %s', str(AE_STATES))
+
+ with its_session_utils.ItsSession(
+ device_id=self.dut.serial,
+ camera_id=self.camera_id,
+ hidden_physical_id=self.hidden_physical_id) as cam:
+ props = cam.get_camera_properties()
+ props = cam.override_with_hidden_physical_camera_props(props)
+ test_name = os.path.join(self.log_path, _TEST_NAME)
+
+ # check SKIP conditions
+ first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
+ camera_properties_utils.skip_unless(
+ camera_properties_utils.flash(props) and
+ first_api_level >= _FLASH_CHECK_FIRST_API_LEVEL)
+
+ # establish connection with lighting controller
+ arduino_serial_port = lighting_control_utils.lighting_control(
+ self.lighting_cntl, self.lighting_ch)
+
+ # turn OFF lights to darken scene
+ lighting_control_utils.set_lighting_state(
+ arduino_serial_port, self.lighting_ch, 'OFF')
+
+ # turn OFF tablet to darken scene
+ if self.tablet:
+ output = self.tablet.adb.shell('dumpsys display | grep mScreenState')
+ output_list = str(output.decode('utf-8')).strip().split(' ')
+ for val in output_list:
+ if 'ON' in val:
+ self.tablet.adb.shell(['input', 'keyevent', 'KEYCODE_POWER'])
+
+ no_flash_exp_x_iso = 0
+ no_flash_mean = 0
+ no_flash_grad = 0
+ flash_exp_x_iso = []
+ flash_means = []
+ flash_grads = []
+
+ # take captures with no flash as baseline: use last frame
+ logging.debug('Taking reference frame(s) with no flash.')
+ cam.do_3a(do_af=False)
+ cap = take_captures(cam)[_NUM_FRAMES-1]
+ metadata = cap['metadata']
+ exp = int(metadata['android.sensor.exposureTime'])
+ iso = int(metadata['android.sensor.sensitivity'])
+ logging.debug('No auto_flash ISO: %d, exp: %d ns', iso, exp)
+ logging.debug('AE_MODE (cap): %s',
+ AE_MODES[metadata['android.control.aeMode']])
+ logging.debug('AE_STATE (cap): %s',
+ AE_STATES[metadata['android.control.aeState']])
+ no_flash_exp_x_iso = exp * iso
+ y, _, _ = image_processing_utils.convert_capture_to_planes(
+ cap, props)
+ patch = image_processing_utils.get_image_patch(
+ y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
+ no_flash_mean = image_processing_utils.compute_image_means(
+ patch)[0]*255
+ no_flash_grad = image_processing_utils.compute_image_max_gradients(
+ patch)[0]*255
+ image_processing_utils.write_image(y, f'{test_name}_no_flash_Y.jpg')
+
+ # log results
+ logging.debug('No flash exposure X ISO %d', no_flash_exp_x_iso)
+ logging.debug('No flash Y grad: %.4f', no_flash_grad)
+ logging.debug('No flash Y mean: %.4f', no_flash_mean)
+
+ # take captures with auto flash enabled
+ logging.debug('Taking frames with auto flash enabled.')
+ cam.do_3a(do_af=False, auto_flash=True)
+ caps = take_captures(cam, auto_flash=True)
+
+ # evaluate captured images
+ for i in range(_NUM_FRAMES):
+ logging.debug('frame # %d', i)
+ metadata = caps[i]['metadata']
+ exp = int(metadata['android.sensor.exposureTime'])
+ iso = int(metadata['android.sensor.sensitivity'])
+ logging.debug('ISO: %d, exp: %d ns', iso, exp)
+ if i == 0:
+ logging.debug('AE_MODE (cap): %s',
+ AE_MODES[metadata['android.control.aeMode']])
+ ae_state = AE_STATES[metadata['android.control.aeState']]
+ logging.debug('AE_STATE (cap): %s', ae_state)
+ if ae_state != AE_STATES[4]: # FLASH_REQUIRED
+ raise AssertionError('Scene not dark enough to trigger auto-flash. '
+ 'Check scene.')
+ flash_exp_x_iso.append(exp*iso)
+ y, _, _ = image_processing_utils.convert_capture_to_planes(
+ caps[i], props)
+ patch = image_processing_utils.get_image_patch(
+ y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
+ flash_means.append(
+ image_processing_utils.compute_image_means(patch)[0]*255)
+ flash_grads.append(
+ image_processing_utils.compute_image_max_gradients(patch)[0]*255)
+
+ image_processing_utils.write_image(
+ y, f'{test_name}_auto_flash_Y_{i}.jpg')
+
+ # log results
+ logging.debug('Flash exposure X ISOs %s', str(flash_exp_x_iso))
+ logging.debug('Flash frames Y grads: %s', str(flash_grads))
+ logging.debug('Flash frames Y means: %s', str(flash_means))
+
+ # turn lights back ON
+ lighting_control_utils.set_lighting_state(
+ arduino_serial_port, self.lighting_ch, 'ON')
+
+ # assert correct behavior
+ grad_delta = max(flash_grads) - no_flash_grad
+ mean_delta = max(flash_means) - no_flash_mean
+ if not (grad_delta > _GRAD_DELTA_ATOL or
+ mean_delta > _MEAN_DELTA_ATOL):
+ raise AssertionError(
+ f'grad FLASH-OFF: {grad_delta:.3f}, ATOL: {_GRAD_DELTA_ATOL}, '
+ f'mean FLASH-OFF: {mean_delta:.3f}, ATOL: {_MEAN_DELTA_ATOL}')
+
+if __name__ == '__main__':
+ test_runner.main()
+
diff --git a/apps/CameraITS/tests/scene2_a/test_faces.py b/apps/CameraITS/tests/scene2_a/test_faces.py
deleted file mode 100644
index 220628d..0000000
--- a/apps/CameraITS/tests/scene2_a/test_faces.py
+++ /dev/null
@@ -1,179 +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.
-"""Verifies faces are detected and landmarks in bounding boxes."""
-
-
-import logging
-import os.path
-from mobly import test_runner
-
-import its_base_test
-import camera_properties_utils
-import capture_request_utils
-import image_processing_utils
-import its_session_utils
-
-NAME = os.path.splitext(os.path.basename(__file__))[0]
-NUM_TEST_FRAMES = 20
-FD_MODE_OFF = 0
-FD_MODE_SIMPLE = 1
-FD_MODE_FULL = 2
-W, H = 640, 480
-
-
-def check_face_bounding_box(rect, aa_w, aa_h):
- """Check that face bounding box is within the active array area."""
- rect_t = rect['top']
- rect_b = rect['bottom']
- rect_l = rect['left']
- rect_r = rect['right']
- if rect_t > rect_b:
- raise AssertionError(f'Face top > bottom! t: {rect_t}, b: {rect_b}')
- if rect_l > rect_r:
- raise AssertionError(f'Face left > right! l: {rect_l}, r: {rect_r}')
-
- if not 0 <= rect_l <= aa_w:
- raise AssertionError(f'Face l: {rect_l} outside of active W: 0,{aa_w}')
- if not 0 <= rect_r <= aa_w:
- raise AssertionError(f'Face r: {rect_r} outside of active W: 0,{aa_w}')
- if not 0 <= rect_t <= aa_h:
- raise AssertionError(f'Face t: {rect_t} outside active H: 0,{aa_h}')
- if not 0 <= rect_b <= aa_h:
- raise AssertionError(f'Face b: {rect_b} outside active H: 0,{aa_h}')
-
-
-def check_face_landmarks(face):
- """Check that face landmarks fall within face bounding box."""
- l, r = face['bounds']['left'], face['bounds']['right']
- t, b = face['bounds']['top'], face['bounds']['bottom']
- l_eye_x, l_eye_y = face['leftEye']['x'], face['leftEye']['y']
- r_eye_x, r_eye_y = face['rightEye']['x'], face['rightEye']['y']
- mouth_x, mouth_y = face['mouth']['x'], face['mouth']['y']
- if not l <= l_eye_x <= r:
- raise AssertionError(f'Face l: {l}, r: {r}, left eye x: {l_eye_x}')
- if not t <= l_eye_y <= b:
- raise AssertionError(f'Face t: {t}, b: {b}, left eye y: {l_eye_y}')
- if not l <= r_eye_x <= r:
- raise AssertionError(f'Face l: {l}, r: {r}, right eye x: {r_eye_x}')
- if not t <= r_eye_y <= b:
- raise AssertionError(f'Face t: {t}, b: {b}, right eye y: {r_eye_y}')
- if not l <= mouth_x <= r:
- raise AssertionError(f'Face l: {l}, r: {r}, mouth x: {mouth_x}')
- if not t <= mouth_y <= b:
- raise AssertionError(f'Face t: {t}, b: {b}, mouth y: {mouth_y}')
-
-
-class FacesTest(its_base_test.ItsBaseTest):
- """Tests face detection algorithms.
-
- Allows NUM_TEST_FRAMES for face detection algorithm to find all faces.
- Tests OFF, SIMPLE, and FULL modes if available.
- OFF --> no faces should be found.
- SIMPLE --> face(s) should be found, but no landmarks.
- FULL --> face(s) should be found and face landmarks reported.
- """
-
- def test_faces(self):
- logging.debug('Starting %s', NAME)
- with its_session_utils.ItsSession(
- device_id=self.dut.serial,
- camera_id=self.camera_id,
- hidden_physical_id=self.hidden_physical_id) as cam:
- props = cam.get_camera_properties()
- props = cam.override_with_hidden_physical_camera_props(props)
-
- # Load chart for scene.
- its_session_utils.load_scene(
- cam, props, self.scene, self.tablet, self.chart_distance)
-
- camera_properties_utils.skip_unless(
- camera_properties_utils.face_detect(props))
- mono_camera = camera_properties_utils.mono_camera(props)
- fd_modes = props['android.statistics.info.availableFaceDetectModes']
- a = props['android.sensor.info.activeArraySize']
- aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
- if camera_properties_utils.read_3a(props):
- gain, exp, _, _, focus = cam.do_3a(
- get_results=True, mono_camera=mono_camera)
- logging.debug('iso = %d', gain)
- logging.debug('exp = %.2fms', (exp * 1.0E-6))
- if focus == 0.0:
- logging.debug('fd = infinity')
- else:
- logging.debug('fd = %.2fcm', (1.0E2 / focus))
- for fd_mode in fd_modes:
- if not FD_MODE_OFF <= fd_mode <= FD_MODE_FULL:
- raise AssertionError(f'fd_mode undefined: {fd_mode}')
- req = capture_request_utils.auto_capture_request()
- req['android.statistics.faceDetectMode'] = fd_mode
- fmt = {'format': 'yuv', 'width': W, 'height': H}
- caps = cam.do_capture([req] * NUM_TEST_FRAMES, fmt)
- for i, cap in enumerate(caps):
- fd_mode_md = cap['metadata']['android.statistics.faceDetectMode']
- if fd_mode_md != fd_mode:
- raise AssertionError('Metadata does not match request! '
- f'Request: {fd_mode} metadata: {fd_mode_md}.')
- faces = cap['metadata']['android.statistics.faces']
-
- # 0 faces should be returned for OFF mode
- if fd_mode == FD_MODE_OFF:
- if faces:
- raise AssertionError('Faces found in OFF mode.')
- continue
- # Save last frame.
- if i == NUM_TEST_FRAMES - 1:
- img = image_processing_utils.convert_capture_to_rgb_image(
- cap, props=props)
- img = image_processing_utils.rotate_img_per_argv(img)
- img_name = '%s_fd_mode_%s.jpg' % (os.path.join(self.log_path,
- NAME), fd_mode)
- image_processing_utils.write_image(img, img_name)
- if not faces:
- raise AssertionError(f'No face detected in mode {fd_mode}.')
- if not faces:
- continue
-
- logging.debug('Frame %d face metadata:', i)
- logging.debug('Faces: %s', faces)
-
- face_scores = [face['score'] for face in faces]
- face_rectangles = [face['bounds'] for face in faces]
- for score in face_scores:
- if not 1 <= score <= 100:
- raise AssertionError(f'Face score not valid! score: {score}.')
- # Face bounds should be within active array.
- for j, rect in enumerate(face_rectangles):
- logging.debug('Checking face rectangle %d', j)
- check_face_bounding_box(rect, aw, ah)
-
- # Face ID should be -1 for SIMPLE and unique for FULL
- if fd_mode == FD_MODE_SIMPLE:
- for face in faces:
- if 'leftEye' in face or 'rightEye' in face:
- raise AssertionError('Eyes not supported in FD_MODE_SIMPLE.')
- if 'mouth' in face:
- raise AssertionError('Mouth not supported in FD_MODE_SIMPLE.')
- if face['id'] != -1:
- raise AssertionError('face_id should be -1 in FD_MODE_SIMPLE.')
- elif fd_mode == FD_MODE_FULL:
- face_ids = [face['id'] for face in faces]
- if len(face_ids) != len(set(face_ids)):
- raise AssertionError('Same face detected more than 1x.')
- # Face landmarks should be within face bounds
- for k, face in enumerate(faces):
- logging.debug('Checking landmarks in face %d: %s', k, str(face))
- check_face_landmarks(face)
-
-if __name__ == '__main__':
- test_runner.main()
diff --git a/apps/CameraITS/tests/scene2_a/test_num_faces.py b/apps/CameraITS/tests/scene2_a/test_num_faces.py
index 9cdca32..304b67b 100644
--- a/apps/CameraITS/tests/scene2_a/test_num_faces.py
+++ b/apps/CameraITS/tests/scene2_a/test_num_faces.py
@@ -34,22 +34,90 @@
W, H = 640, 480
-def draw_face_rectangles(img, faces, aw, ah):
+def check_face_bounding_box(rect, aw, ah, index):
+ """Checks face bounding box is within the active array area.
+
+ Args:
+ rect: dict; with face bounding box information
+ aw: int; active array width
+ ah: int; active array height
+ index: int to designate face number
+ """
+ logging.debug('Checking bounding box in face %d: %s', index, str(rect))
+ if (rect['top'] >= rect['bottom'] or
+ rect['left'] >= rect['right']):
+ raise AssertionError('Face coordinates incorrect! '
+ f" t: {rect['top']}, b: {rect['bottom']}, "
+ f" l: {rect['left']}, r: {rect['right']}")
+ if (not 0 <= rect['top'] <= ah or
+ not 0 <= rect['bottom'] <= ah):
+ raise AssertionError('Face top/bottom outside of image height! '
+ f"t: {rect['top']}, b: {rect['bottom']}, "
+ f"h: {ah}")
+ if (not 0 <= rect['left'] <= aw or
+ not 0 <= rect['right'] <= aw):
+ raise AssertionError('Face left/right outside of image width! '
+ f"l: {rect['left']}, r: {rect['right']}, "
+ f" w: {aw}")
+
+
+def check_face_landmarks(face, fd_mode, index):
+ """Checks face landmarks fall within face bounding box.
+
+ Face ID should be -1 for SIMPLE and unique for FULL
+ Args:
+ face: dict from face detection algorithm
+ fd_mode: int of face detection mode
+ index: int to designate face number
+ """
+ logging.debug('Checking landmarks in face %d: %s', index, str(face))
+ if fd_mode == FD_MODE_SIMPLE:
+ if 'leftEye' in face or 'rightEye' in face:
+ raise AssertionError('Eyes not supported in FD_MODE_SIMPLE.')
+ if 'mouth' in face:
+ raise AssertionError('Mouth not supported in FD_MODE_SIMPLE.')
+ if face['id'] != -1:
+ raise AssertionError('face_id should be -1 in FD_MODE_SIMPLE.')
+ elif fd_mode == FD_MODE_FULL:
+ l, r = face['bounds']['left'], face['bounds']['right']
+ t, b = face['bounds']['top'], face['bounds']['bottom']
+ l_eye_x, l_eye_y = face['leftEye']['x'], face['leftEye']['y']
+ r_eye_x, r_eye_y = face['rightEye']['x'], face['rightEye']['y']
+ mouth_x, mouth_y = face['mouth']['x'], face['mouth']['y']
+ if not l <= l_eye_x <= r:
+ raise AssertionError(f'Face l: {l}, r: {r}, left eye x: {l_eye_x}')
+ if not t <= l_eye_y <= b:
+ raise AssertionError(f'Face t: {t}, b: {b}, left eye y: {l_eye_y}')
+ if not l <= r_eye_x <= r:
+ raise AssertionError(f'Face l: {l}, r: {r}, right eye x: {r_eye_x}')
+ if not t <= r_eye_y <= b:
+ raise AssertionError(f'Face t: {t}, b: {b}, right eye y: {r_eye_y}')
+ if not l <= mouth_x <= r:
+ raise AssertionError(f'Face l: {l}, r: {r}, mouth x: {mouth_x}')
+ if not t <= mouth_y <= b:
+ raise AssertionError(f'Face t: {t}, b: {b}, mouth y: {mouth_y}')
+ else:
+ raise AssertionError(f'Unknown face detection mode: {fd_mode}.')
+
+
+def draw_face_rectangles(img, faces, crop):
"""Draw rectangles on top of image.
Args:
img: image array
faces: list of dicts with face information
- aw: int; active array width
- ah: int; active array height
+ crop: dict; crop region size with 'top, right, left, bottom' as keys
Returns:
img with face rectangles drawn on it
"""
+ cw, ch = crop['right'] - crop['left'], crop['bottom'] - crop['top']
+ logging.debug('crop region: %s', str(crop))
for rect in [face['bounds'] for face in faces]:
- top_left = (int(round(rect['left']*W/aw)),
- int(round(rect['top']*H/ah)))
- bot_rght = (int(round(rect['right']*W/aw)),
- int(round(rect['bottom']*H/ah)))
+ logging.debug('rect: %s', str(rect))
+ top_left = (int(round((rect['left'] - crop['left']) * img.shape[1] / cw)),
+ int(round((rect['top'] - crop['top']) * img.shape[0] / ch)))
+ bot_rght = (int(round((rect['right'] - crop['left']) * img.shape[1] / cw)),
+ int(round((rect['bottom'] - crop['top']) * img.shape[0] / ch)))
cv2.rectangle(img, top_left, bot_rght, (0, 1, 0), 2)
return img
@@ -78,24 +146,31 @@
fd_modes = props['android.statistics.info.availableFaceDetectModes']
a = props['android.sensor.info.activeArraySize']
aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
+ logging.debug('active array size: %s', str(a))
+ file_name_stem = os.path.join(self.log_path, NAME)
if camera_properties_utils.read_3a(props):
_, _, _, _, _ = cam.do_3a(get_results=True, mono_camera=mono_camera)
for fd_mode in fd_modes:
- assert FD_MODE_OFF <= fd_mode <= FD_MODE_FULL
+ logging.debug('face detection mode: %d', fd_mode)
+ if not FD_MODE_OFF <= fd_mode <= FD_MODE_FULL:
+ raise AssertionError(f'FD mode {fd_mode} not in MODES! '
+ f'OFF: {FD_MODE_OFF}, FULL: {FD_MODE_FULL}')
req = capture_request_utils.auto_capture_request()
req['android.statistics.faceDetectMode'] = fd_mode
fmt = {'format': 'yuv', 'width': W, 'height': H}
caps = cam.do_capture([req]*NUM_TEST_FRAMES, fmt)
for i, cap in enumerate(caps):
- md = cap['metadata']
- assert md['android.statistics.faceDetectMode'] == fd_mode
- faces = md['android.statistics.faces']
+ fd_mode_cap = cap['metadata']['android.statistics.faceDetectMode']
+ if fd_mode_cap != fd_mode:
+ raise AssertionError(f'metadata {fd_mode_cap} != req {fd_mode}')
+ faces = cap['metadata']['android.statistics.faces']
# 0 faces should be returned for OFF mode
if fd_mode == FD_MODE_OFF:
- assert not faces
+ if faces:
+ raise AssertionError(f'Error: faces detected in OFF: {faces}')
continue
# Face detection could take several frames to warm up,
# but should detect the correct number of faces in last frame
@@ -106,12 +181,14 @@
logging.debug('Found %d face(s), expected %d.',
fnd_faces, NUM_FACES)
# draw boxes around faces
- img = draw_face_rectangles(img, faces, aw, ah)
+ crop_region = cap['metadata']['android.scaler.cropRegion']
+ img = draw_face_rectangles(img, faces, crop_region)
# save image with rectangles
- img_name = '%s_fd_mode_%s.jpg' % (os.path.join(self.log_path,
- NAME), fd_mode)
+ img_name = f'{file_name_stem}_fd_mode_{fd_mode}.jpg'
image_processing_utils.write_image(img, img_name)
- assert fnd_faces == NUM_FACES
+ if fnd_faces != NUM_FACES:
+ raise AssertionError('Wrong num of faces found! '
+ f'Found: {fnd_faces}, expected: {NUM_FACES}')
if not faces:
continue
@@ -121,16 +198,18 @@
# Reasonable scores for faces
face_scores = [face['score'] for face in faces]
for score in face_scores:
- assert 1 <= score <= 100
+ if not 1 <= score <= 100:
+ raise AssertionError(f'score not between [1:100]! {score}')
+
# Face bounds should be within active array
face_rectangles = [face['bounds'] for face in faces]
- for rect in face_rectangles:
- assert rect['top'] < rect['bottom']
- assert rect['left'] < rect['right']
- assert 0 <= rect['top'] <= ah
- assert 0 <= rect['bottom'] <= ah
- assert 0 <= rect['left'] <= aw
- assert 0 <= rect['right'] <= aw
+ for j, rect in enumerate(face_rectangles):
+ check_face_bounding_box(rect, aw, ah, j)
+
+ # Face landmarks (if provided) are within face bounding box
+ for k, face in enumerate(faces):
+ check_face_landmarks(face, fd_mode, k)
+
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene2_c/test_camera_launch_perf_class.py b/apps/CameraITS/tests/scene2_c/test_camera_launch_perf_class.py
index e1ed893..e7c7af8 100644
--- a/apps/CameraITS/tests/scene2_c/test_camera_launch_perf_class.py
+++ b/apps/CameraITS/tests/scene2_c/test_camera_launch_perf_class.py
@@ -22,13 +22,14 @@
import its_base_test
import its_session_utils
+# This must match MPC12_CAMERA_LAUNCH_THRESHOLD in ItsTestActivity.java
CAMERA_LAUNCH_S_PERFORMANCE_CLASS_THRESHOLD = 600 # ms
class CameraLaunchSPerfClassTest(its_base_test.ItsBaseTest):
"""Test camera launch latency for S performance class as specified in CDD.
- [7.5/H-1-7] MUST have camera2 startup latency (open camera to first preview
+ [7.5/H-1-6] MUST have camera2 startup latency (open camera to first preview
frame) < 600ms as measured by the CTS camera PerformanceTest under ITS
lighting conditions (3000K) for both primary cameras.
"""
@@ -41,7 +42,7 @@
camera_id=self.camera_id) as cam:
camera_properties_utils.skip_unless(
- cam.is_performance_class_primary_camera())
+ cam.is_primary_camera())
# Load chart for scene.
props = cam.get_camera_properties()
@@ -55,11 +56,17 @@
camera_id=self.camera_id)
launch_ms = cam.measure_camera_launch_ms()
- if launch_ms >= CAMERA_LAUNCH_S_PERFORMANCE_CLASS_THRESHOLD:
- raise AssertionError(f'camera launch time: {launch_ms} ms, THRESH: '
- f'{CAMERA_LAUNCH_S_PERFORMANCE_CLASS_THRESHOLD} ms')
- else:
- logging.debug('camera launch time: %.1f ms', launch_ms)
+
+ # Assert launch time if device claims performance class
+ if (cam.is_performance_class() and
+ launch_ms >= CAMERA_LAUNCH_S_PERFORMANCE_CLASS_THRESHOLD):
+ raise AssertionError(f'camera_launch_time_ms: {launch_ms}, THRESH: '
+ f'{CAMERA_LAUNCH_S_PERFORMANCE_CLASS_THRESHOLD}')
+
+ # Log launch time, so that the corresponding MPC level can be written to
+ # report log. Text must match MPC12_CAMERA_LAUNCH_PATTERN in
+ # ItsTestActivity.java.
+ print(f'camera_launch_time_ms:{launch_ms}')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene2_c/test_jpeg_capture_perf_class.py b/apps/CameraITS/tests/scene2_c/test_jpeg_capture_perf_class.py
index ba4867b..0eb76eb 100644
--- a/apps/CameraITS/tests/scene2_c/test_jpeg_capture_perf_class.py
+++ b/apps/CameraITS/tests/scene2_c/test_jpeg_capture_perf_class.py
@@ -22,13 +22,14 @@
import its_base_test
import its_session_utils
+# This must match MPC12_JPEG_CAPTURE_THRESHOLD in ItsTestActivity.java
JPEG_CAPTURE_S_PERFORMANCE_CLASS_THRESHOLD = 1000 # ms
class JpegCaptureSPerfClassTest(its_base_test.ItsBaseTest):
"""Test jpeg capture latency for S performance class as specified in CDD.
- [7.5/H-1-6] MUST have camera2 JPEG capture latency < 1000ms for 1080p
+ [7.5/H-1-5] MUST have camera2 JPEG capture latency < 1000ms for 1080p
resolution as measured by the CTS camera PerformanceTest under ITS lighting
conditions (3000K) for both primary cameras.
"""
@@ -41,7 +42,7 @@
camera_id=self.camera_id) as cam:
camera_properties_utils.skip_unless(
- cam.is_performance_class_primary_camera())
+ cam.is_primary_camera())
# Load chart for scene.
props = cam.get_camera_properties()
@@ -55,12 +56,18 @@
camera_id=self.camera_id)
jpeg_capture_ms = cam.measure_camera_1080p_jpeg_capture_ms()
- if jpeg_capture_ms >= JPEG_CAPTURE_S_PERFORMANCE_CLASS_THRESHOLD:
- raise AssertionError(f'1080p jpeg capture time: {jpeg_capture_ms} ms, '
+
+ # Assert jpeg capture time if device claims performance class
+ if (cam.is_performance_class() and
+ jpeg_capture_ms >= JPEG_CAPTURE_S_PERFORMANCE_CLASS_THRESHOLD):
+ raise AssertionError(f'1080p_jpeg_capture_time_ms: {jpeg_capture_ms}, '
f'THRESH: '
- f'{JPEG_CAPTURE_S_PERFORMANCE_CLASS_THRESHOLD} ms')
- else:
- logging.debug('1080p jpeg capture time: %.1f ms', jpeg_capture_ms)
+ f'{JPEG_CAPTURE_S_PERFORMANCE_CLASS_THRESHOLD}')
+
+ # Log jpeg capture time so that the corresponding MPC level can be written
+ # to report log. Text must match MPC12_JPEG_CAPTURE_PATTERN in
+ # ItsTestActivity.java.
+ print(f'1080p_jpeg_capture_time_ms:{jpeg_capture_ms}')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene3/test_edge_enhancement.py b/apps/CameraITS/tests/scene3/test_edge_enhancement.py
index 9539971..c6a3bcb 100644
--- a/apps/CameraITS/tests/scene3/test_edge_enhancement.py
+++ b/apps/CameraITS/tests/scene3/test_edge_enhancement.py
@@ -94,7 +94,6 @@
device_id=self.dut.serial,
camera_id=self.camera_id,
hidden_physical_id=self.hidden_physical_id) as cam:
- chart_loc_arg = self.chart_loc_arg
props = cam.get_camera_properties()
props = cam.override_with_hidden_physical_camera_props(props)
@@ -109,8 +108,7 @@
cam, props, self.scene, self.tablet, self.chart_distance)
# Initialize chart class and locate chart in scene
- chart = opencv_processing_utils.Chart(
- cam, props, self.log_path, chart_loc=chart_loc_arg)
+ chart = opencv_processing_utils.Chart(cam, props, self.log_path)
# Define format
fmt = 'yuv'
@@ -141,24 +139,26 @@
str(sharpness_regular))
logging.debug('Verify HQ is sharper than OFF')
- e_msg = 'HQ: %.3f, OFF: %.3f' % (sharpness_regular[EDGE_MODES['HQ']],
- sharpness_regular[EDGE_MODES['OFF']])
- assert (sharpness_regular[EDGE_MODES['HQ']] >
- sharpness_regular[EDGE_MODES['OFF']]), e_msg
+ if (sharpness_regular[EDGE_MODES['HQ']] <=
+ sharpness_regular[EDGE_MODES['OFF']]):
+ raise AssertionError(f"HQ: {sharpness_regular[EDGE_MODES['HQ']]:.3f}, "
+ f"OFF: {sharpness_regular[EDGE_MODES['OFF']]:.3f}")
logging.debug('Verify OFF is not sharper than FAST')
- e_msg = 'FAST: %.3f, OFF: %.3f, RTOL: %.2f' % (
- sharpness_regular[EDGE_MODES['FAST']],
- sharpness_regular[EDGE_MODES['OFF']], SHARPNESS_RTOL)
- assert (sharpness_regular[EDGE_MODES['FAST']] >
- sharpness_regular[EDGE_MODES['OFF']]*(1.0-SHARPNESS_RTOL)), e_msg
+ if (sharpness_regular[EDGE_MODES['FAST']] <=
+ sharpness_regular[EDGE_MODES['OFF']]*(1.0-SHARPNESS_RTOL)):
+ raise AssertionError(
+ f"FAST: {sharpness_regular[EDGE_MODES['FAST']]:.3f}, "
+ f"OFF: {sharpness_regular[EDGE_MODES['OFF']]:.3f}, "
+ f"RTOL: {SHARPNESS_RTOL}")
logging.debug('Verify FAST is not sharper than HQ')
- e_msg = 'HQ: %.3f, FAST: %.3f, RTOL: %.2f' % (
- sharpness_regular[EDGE_MODES['HQ']],
- sharpness_regular[EDGE_MODES['FAST']], SHARPNESS_RTOL)
- assert (sharpness_regular[EDGE_MODES['HQ']] >
- sharpness_regular[EDGE_MODES['FAST']]*(1.0-SHARPNESS_RTOL)), e_msg
+ if (sharpness_regular[EDGE_MODES['HQ']] <=
+ sharpness_regular[EDGE_MODES['FAST']]*(1.0-SHARPNESS_RTOL)):
+ raise AssertionError(
+ f"HQ: {sharpness_regular[EDGE_MODES['HQ']]:.3f}, "
+ f"FAST: {sharpness_regular[EDGE_MODES['FAST']]:.3f}, "
+ f"RTOL: {SHARPNESS_RTOL}")
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene3/test_flip_mirror.py b/apps/CameraITS/tests/scene3/test_flip_mirror.py
index 2dff574..f190477 100644
--- a/apps/CameraITS/tests/scene3/test_flip_mirror.py
+++ b/apps/CameraITS/tests/scene3/test_flip_mirror.py
@@ -137,15 +137,13 @@
props = cam.get_camera_properties()
props = cam.override_with_hidden_physical_camera_props(props)
debug = self.debug_mode
- chart_loc_arg = self.chart_loc_arg
# load chart for scene
its_session_utils.load_scene(
cam, props, self.scene, self.tablet, self.chart_distance)
# initialize chart class and locate chart in scene
- chart = opencv_processing_utils.Chart(
- cam, props, self.log_path, chart_loc=chart_loc_arg)
+ chart = opencv_processing_utils.Chart(cam, props, self.log_path)
fmt = {'format': 'yuv', 'width': VGA_W, 'height': VGA_H}
# test that image is not flipped, mirrored, or rotated
diff --git a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
index 3e638d6..930817c 100644
--- a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
+++ b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
@@ -105,7 +105,6 @@
device_id=self.dut.serial,
camera_id=self.camera_id,
hidden_physical_id=self.hidden_physical_id) as cam:
- chart_loc_arg = self.chart_loc_arg
props = cam.get_camera_properties()
props = cam.override_with_hidden_physical_camera_props(props)
@@ -120,8 +119,7 @@
cam, props, self.scene, self.tablet, self.chart_distance)
# Initialize chart class and locate chart in scene
- chart = opencv_processing_utils.Chart(
- cam, props, self.log_path, chart_loc=chart_loc_arg)
+ chart = opencv_processing_utils.Chart(cam, props, self.log_path)
# Get proper sensitivity, exposure time, and focus distance with 3A.
mono_camera = camera_properties_utils.mono_camera(props)
diff --git a/apps/CameraITS/tests/scene3/test_lens_position.py b/apps/CameraITS/tests/scene3/test_lens_position.py
deleted file mode 100644
index 09f74f0..0000000
--- a/apps/CameraITS/tests/scene3/test_lens_position.py
+++ /dev/null
@@ -1,232 +0,0 @@
-# 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.
-"""Verifies android.lens.focusDistance for lens moving and stationary."""
-
-
-import logging
-import os
-from mobly import test_runner
-import numpy as np
-
-import its_base_test
-import camera_properties_utils
-import capture_request_utils
-import error_util
-import image_processing_utils
-import its_session_utils
-import opencv_processing_utils
-
-FRAME_ATOL_MS = 10 # ms
-LENS_MOVING_STATE = 1
-NAME = os.path.splitext(os.path.basename(__file__))[0]
-NSEC_TO_MSEC = 1.0E-6
-NUM_TRYS = 2
-NUM_STEPS = 6
-POSITION_RTOL = 0.1
-SHARPNESS_RTOL = 0.1
-VGA_W, VGA_H = 640, 480
-
-
-def assert_static_frames_behavior(d_stat):
- """Assert locations/sharpness are correct in static frames."""
- logging.debug('Asserting static lens locations/sharpness are similar')
- for i in range(len(d_stat) // 2):
- j = 2 * NUM_STEPS - 1 - i
- rw_msg = 'fd_write: %.3f, fd_read: %.3f, RTOL: %.2f' % (
- d_stat[i]['fd'], d_stat[i]['loc'], POSITION_RTOL)
- fr_msg = 'loc_fwd[%d]: %.3f, loc_rev[%d]: %.3f, RTOL: %.2f' % (
- i, d_stat[i]['loc'], j, d_stat[j]['loc'], POSITION_RTOL)
- s_msg = 'sharpness_fwd: %.3f, sharpness_rev: %.3f, RTOL: %.2f' % (
- d_stat[i]['sharpness'], d_stat[j]['sharpness'], SHARPNESS_RTOL)
- assert np.isclose(d_stat[i]['loc'], d_stat[i]['fd'],
- rtol=POSITION_RTOL), rw_msg
- assert np.isclose(d_stat[i]['loc'], d_stat[j]['loc'],
- rtol=POSITION_RTOL), fr_msg
- assert np.isclose(d_stat[i]['sharpness'], d_stat[j]['sharpness'],
- rtol=SHARPNESS_RTOL), s_msg
-
-
-def assert_moving_frames_behavior(d_move, d_stat):
- """Assert locations/sharpness are correct for consecutive moving frames."""
- logging.debug('Asserting moving frames are consecutive')
- times = [v['timestamp'] for v in d_move.values()]
- diffs = np.gradient(times)
- assert np.isclose(np.amin(diffs), np.amax(diffs),
- atol=FRAME_ATOL_MS), 'ATOL(ms): %.1f' % FRAME_ATOL_MS
-
- logging.debug('Asserting moving lens locations/sharpness are similar')
- for i in range(len(d_move)):
- e_msg = 'static: %.3f, moving: %.3f, RTOL: %.2f' % (
- d_stat[i]['loc'], d_move[i]['loc'], POSITION_RTOL)
- assert np.isclose(d_stat[i]['loc'], d_move[i]['loc'],
- rtol=POSITION_RTOL), e_msg
- if d_move[i]['lens_moving'] and i > 0:
- e_msg = '%d sharpness[stat]: %.2f ' % (i-1, d_stat[i-1]['sharpness'])
- e_msg += '%d sharpness[stat]: %.2f, [move]: %.2f, RTOL: %.1f' % (
- i, d_stat[i]['sharpness'], d_move[i]['sharpness'], SHARPNESS_RTOL)
- if d_stat[i]['sharpness'] > d_stat[i-1]['sharpness']:
- assert (d_stat[i]['sharpness'] * (1.0 + SHARPNESS_RTOL) >
- d_move[i]['sharpness'] > d_stat[i-1]['sharpness'] *
- (1.0 - SHARPNESS_RTOL)), e_msg
- else:
- assert (d_stat[i-1]['sharpness'] * (1.0 + SHARPNESS_RTOL) >
- d_move[i]['sharpness'] > d_stat[i]['sharpness'] *
- (1.0 - SHARPNESS_RTOL)), e_msg
- elif not d_move[i]['lens_moving']:
- e_msg = '%d sharpness[stat]: %.2f, [move]: %.2f, RTOL: %.1f' % (
- i, d_stat[i]['sharpness'], d_move[i]['sharpness'], SHARPNESS_RTOL)
- assert np.isclose(d_stat[i]['sharpness'], d_move[i]['sharpness'],
- rtol=SHARPNESS_RTOL), e_msg
- else:
- raise error_util.Error('Lens is moving at frame 0!')
-
-
-def take_caps_and_return_data(cam, props, fmt, sens, exp, chart, log_path):
- """Return fd, sharpness, lens state of the output images.
-
- Args:
- cam: An open device session
- props: Properties of cam
- fmt: Dict for capture format
- sens: Sensitivity for 3A request as defined in android.sensor.sensitivity
- exp: Exposure time for 3A request as defined in android.sensor.exposureTime
- chart: Object with chart properties
- log_path: Location to save images
-
- Returns:
- Dictionary of results for different focal distance captures with static
- lens positions and moving lens positions: d_static, d_moving
- """
-
- # initialize variables and take data sets
- data_static = {}
- data_moving = {}
- white_level = int(props['android.sensor.info.whiteLevel'])
- min_fd = props['android.lens.info.minimumFocusDistance']
- hyperfocal = props['android.lens.info.hyperfocalDistance']
- # create forward + back list of focal distances
- fds_f = np.arange(hyperfocal, min_fd, (min_fd-hyperfocal)/(NUM_STEPS-1))
- fds_f = np.append(fds_f, min_fd)
- fds_fb = list(fds_f) + list(reversed(fds_f))
-
- # take static data set
- for i, fd in enumerate(fds_fb):
- req = capture_request_utils.manual_capture_request(sens, exp)
- req['android.lens.focusDistance'] = fd
- cap = image_processing_utils.stationary_lens_cap(cam, req, fmt)
- data = {'fd': fds_fb[i]}
- data['loc'] = cap['metadata']['android.lens.focusDistance']
- y, _, _ = image_processing_utils.convert_capture_to_planes(cap, props)
- chart.img = image_processing_utils.normalize_img(
- image_processing_utils.get_image_patch(y, chart.xnorm, chart.ynorm,
- chart.wnorm, chart.hnorm))
- image_processing_utils.write_image(chart.img, '%s_stat_i=%d_chart.jpg' % (
- os.path.join(log_path, NAME), i))
- data['sharpness'] = white_level*image_processing_utils.compute_image_sharpness(
- chart.img)
- data_static[i] = data
-
- # take moving data set
- reqs = []
- for i, fd in enumerate(fds_f):
- reqs.append(capture_request_utils.manual_capture_request(sens, exp))
- reqs[i]['android.lens.focusDistance'] = fd
- caps = cam.do_capture(reqs, fmt)
- for i, cap in enumerate(caps):
- data = {'fd': fds_f[i]}
- data['loc'] = cap['metadata']['android.lens.focusDistance']
- data['lens_moving'] = (
- cap['metadata']['android.lens.state'] == LENS_MOVING_STATE)
- timestamp = cap['metadata']['android.sensor.timestamp'] * NSEC_TO_MSEC
- if i == 0:
- timestamp_init = timestamp
- timestamp -= timestamp_init
- data['timestamp'] = timestamp
- y, _, _ = image_processing_utils.convert_capture_to_planes(cap, props)
- y = image_processing_utils.rotate_img_per_argv(y)
- chart.img = image_processing_utils.normalize_img(
- image_processing_utils.get_image_patch(
- y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm))
- image_processing_utils.write_image(chart.img, '%s_move_i=%d_chart.jpg' % (
- os.path.join(log_path, NAME), i))
- data['sharpness'] = (
- white_level * image_processing_utils.compute_image_sharpness(chart.img))
- data_moving[i] = data
- return data_static, data_moving
-
-
-class LensPositionReportingTest(its_base_test.ItsBaseTest):
- """Test if focus position is properly reported for moving lenses."""
-
- def test_lens_position_reporting(self):
- logging.debug('Starting %s', NAME)
- with its_session_utils.ItsSession(
- device_id=self.dut.serial,
- camera_id=self.camera_id,
- hidden_physical_id=self.hidden_physical_id) as cam:
- chart_loc_arg = self.chart_loc_arg
- props = cam.get_camera_properties()
- props = cam.override_with_hidden_physical_camera_props(props)
- log_path = self.log_path
-
- # Check skip conditions
- camera_properties_utils.skip_unless(
- not camera_properties_utils.fixed_focus(props) and
- camera_properties_utils.read_3a(props) and
- camera_properties_utils.lens_calibrated(props))
-
- # Calculate camera_fov and load scaled image on tablet.
- its_session_utils.load_scene(cam, props, self.scene, self.tablet,
- self.chart_distance)
-
- # Initialize chart class and locate chart in scene
- chart = opencv_processing_utils.Chart(
- cam, props, self.log_path, chart_loc=chart_loc_arg)
-
- # Initialize capture format
- fmt = {'format': 'yuv', 'width': VGA_W, 'height': VGA_H}
-
- # Get proper sensitivity and exposure time with 3A
- mono_camera = camera_properties_utils.mono_camera(props)
- s, e, _, _, _ = cam.do_3a(get_results=True, mono_camera=mono_camera)
-
- # Take caps and get sharpness for each focal distance
- d_stat, d_move = take_caps_and_return_data(
- cam, props, fmt, s, e, chart, log_path)
-
- # Summarize info for log file and easier debug
- logging.debug('Lens stationary')
- for k in sorted(d_stat):
- logging.debug(
- 'i: %d\tfd: %.3f\tlens location (diopters): %.3f \t'
- 'sharpness: %.1f', k, d_stat[k]['fd'], d_stat[k]['loc'],
- d_stat[k]['sharpness'])
- logging.debug('Lens moving')
- for k in sorted(d_move):
- logging.debug(
- 'i: %d\tfd: %.3f\tlens location (diopters): %.3f \t'
- 'sharpness: %.1f \tlens_moving: %r \t'
- 'timestamp: %.1fms', k, d_move[k]['fd'], d_move[k]['loc'],
- d_move[k]['sharpness'], d_move[k]['lens_moving'],
- d_move[k]['timestamp'])
-
- # assert reported location/sharpness is correct in static frames
- assert_static_frames_behavior(d_stat)
-
- # assert reported location/sharpness is correct in moving frames
- assert_moving_frames_behavior(d_move, d_stat)
-
-
-if __name__ == '__main__':
- test_runner.main()
diff --git a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
index b19ac1f..c5e9b19 100644
--- a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
+++ b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
@@ -16,6 +16,7 @@
import logging
import os
+import math
import matplotlib
from matplotlib import pylab
from mobly import test_runner
@@ -44,25 +45,25 @@
f"OFF: {sharpness[EDGE_MODES['OFF']]:.5f}")
logging.debug('Verify ZSL is similar to OFF')
- e_msg = 'ZSL: %.5f, OFF: %.5f, RTOL: %.2f' % (
- sharpness[EDGE_MODES['ZSL']], sharpness[EDGE_MODES['OFF']],
- SHARPNESS_RTOL)
- assert np.isclose(sharpness[EDGE_MODES['ZSL']], sharpness[EDGE_MODES['OFF']],
- SHARPNESS_RTOL), e_msg
+ if not math.isclose(sharpness[EDGE_MODES['ZSL']],
+ sharpness[EDGE_MODES['OFF']], rel_tol=SHARPNESS_RTOL):
+ raise AssertionError(f"ZSL: {sharpness[EDGE_MODES['ZSL']]:.5f}, "
+ f"OFF: {sharpness[EDGE_MODES['OFF']]:.5f}, "
+ f'RTOL: {SHARPNESS_RTOL}')
logging.debug('Verify OFF is not sharper than FAST')
- e_msg = 'FAST: %.5f, OFF: %.5f, RTOL: %.2f' % (
- sharpness[EDGE_MODES['FAST']], sharpness[EDGE_MODES['OFF']],
- SHARPNESS_RTOL)
- assert (sharpness[EDGE_MODES['FAST']] >
- sharpness[EDGE_MODES['OFF']] * (1.0-SHARPNESS_RTOL)), e_msg
+ if (sharpness[EDGE_MODES['FAST']] <=
+ sharpness[EDGE_MODES['OFF']] * (1.0-SHARPNESS_RTOL)):
+ raise AssertionError(f"FAST: {sharpness[EDGE_MODES['FAST']]:.5f}, "
+ f"OFF: {sharpness[EDGE_MODES['OFF']]:.5f}, "
+ f'RTOL: {SHARPNESS_RTOL}')
logging.debug('Verify FAST is not sharper than HQ')
- e_msg = 'FAST: %.5f, HQ: %.5f, RTOL: %.2f' % (
- sharpness[EDGE_MODES['FAST']], sharpness[EDGE_MODES['HQ']],
- SHARPNESS_RTOL)
- assert (sharpness[EDGE_MODES['HQ']] >
- sharpness[EDGE_MODES['FAST']] * (1.0-SHARPNESS_RTOL)), e_msg
+ if (sharpness[EDGE_MODES['HQ']] <=
+ sharpness[EDGE_MODES['FAST']] * (1.0-SHARPNESS_RTOL)):
+ raise AssertionError(f"FAST: {sharpness[EDGE_MODES['FAST']]:.5f}, "
+ f"HQ: {sharpness[EDGE_MODES['HQ']]:.5f}, "
+ f'RTOL: {SHARPNESS_RTOL}')
def do_capture_and_determine_sharpness(
@@ -139,7 +140,6 @@
device_id=self.dut.serial,
camera_id=self.camera_id,
hidden_physical_id=self.hidden_physical_id) as cam:
- chart_loc_arg = self.chart_loc_arg
props = cam.get_camera_properties()
props = cam.override_with_hidden_physical_camera_props(props)
log_path = self.log_path
@@ -157,12 +157,11 @@
cam, props, self.scene, self.tablet, self.chart_distance)
# Initialize chart class and locate chart in scene
- chart = opencv_processing_utils.Chart(
- cam, props, self.log_path, chart_loc=chart_loc_arg)
+ chart = opencv_processing_utils.Chart(cam, props, self.log_path)
# If reprocessing is supported, ZSL edge mode must be avaiable.
- assert camera_properties_utils.edge_mode(
- props, EDGE_MODES['ZSL']), 'ZSL android.edge.mode not available!'
+ if not camera_properties_utils.edge_mode(props, EDGE_MODES['ZSL']):
+ raise AssertionError('ZSL android.edge.mode not available!')
reprocess_formats = []
if camera_properties_utils.yuv_reprocess(props):
@@ -251,11 +250,13 @@
hq_div_off_regular = (
sharpness_regular[EDGE_MODES['HQ']] /
sharpness_regular[EDGE_MODES['OFF']])
- e_msg = 'HQ/OFF_reprocess: %.4f, HQ/OFF_reg: %.4f, RTOL: %.2f' % (
- hq_div_off_reprocess, hq_div_off_regular, SHARPNESS_RTOL)
logging.debug('Verify reprocess HQ ~= reg HQ relative to OFF')
- assert np.isclose(hq_div_off_reprocess, hq_div_off_regular,
- SHARPNESS_RTOL), e_msg
+ if not math.isclose(hq_div_off_reprocess, hq_div_off_regular,
+ rel_tol=SHARPNESS_RTOL):
+ raise AssertionError(f'HQ/OFF_reprocess: {hq_div_off_reprocess:.4f}, '
+ f'HQ/OFF_reg: {hq_div_off_regular:.4f}, '
+ f'RTOL: {SHARPNESS_RTOL}')
+
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
index 73d1276..4ed835b 100644
--- a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
+++ b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
@@ -126,9 +126,9 @@
else:
logging.debug('Skipping camera. Not appropriate for test rig.')
- e_msg = 'Error: started with 2+ cameras, reduced to <2. Wrong test rig?'
- e_msg += '\ntest_ids: %s' % str(test_ids)
- assert len(test_ids) >= 2, e_msg
+ if len(test_ids) < 2:
+ raise AssertionError('Error: started with 2+ cameras, reduced to <2 based '
+ f'on FoVs. Wrong test rig? test_ids: {test_ids}')
return test_ids[0:2]
@@ -562,9 +562,10 @@
err_mm = np.linalg.norm(np.array([x_w[i_ref], y_w[i_ref]]) -
np.array([x_w[i_2nd], y_w[i_2nd]])) * M_TO_MM
logging.debug('Center location err (mm): %.2f', err_mm)
- msg = 'Center locations %s <-> %s too different!' % (i_ref, i_2nd)
- msg += ' val=%.2f, ATOL=%.f mm' % (err_mm, ALIGN_TOL_MM)
- assert err_mm < ALIGN_TOL_MM, msg
+ if err_mm > ALIGN_TOL_MM:
+ raise AssertionError(
+ f'Centers {i_ref} <-> {i_2nd} too different! '
+ f'val={err_mm:.2f}, ATOL={ALIGN_TOL_MM} mm')
# Check projections back into pixel space
for i in [i_ref, i_2nd]:
@@ -572,9 +573,9 @@
np.array([x_p[i], y_p[i]]).reshape(1, -1))
logging.debug('Camera %s projection error (pixels): %.1f', i, err)
tol = ALIGN_TOL * sensor_diag[i]
- msg = 'Camera %s project location too different!' % i
- msg += ' diff=%.2f, ATOL=%.2f pixels' % (err, tol)
- assert err < tol, msg
+ if err >= tol:
+ raise AssertionError(f'Camera {i} project location too different! '
+ f'diff={err:.2f}, ATOL={tol:.2f} pixels')
# Check focal length and circle size if more than 1 focal length
if len(fl) > 1:
@@ -584,11 +585,12 @@
fl[i_ref], fl[i_2nd])
logging.debug('Pixel size (um); ref: %.2f, 2nd: %.2f',
pixel_sizes[i_ref], pixel_sizes[i_2nd])
- msg = 'Circle size scales improperly! RTOL=%.1f\n' % CIRCLE_RTOL
- msg += 'Metric: radius/focal_length*sensor_diag should be equal.'
- assert np.isclose(circle[i_ref]['r']*pixel_sizes[i_ref]/fl[i_ref],
- circle[i_2nd]['r']*pixel_sizes[i_2nd]/fl[i_2nd],
- rtol=CIRCLE_RTOL), msg
+ if not math.isclose(circle[i_ref]['r']*pixel_sizes[i_ref]/fl[i_ref],
+ circle[i_2nd]['r']*pixel_sizes[i_2nd]/fl[i_2nd],
+ rel_tol=CIRCLE_RTOL):
+ raise AssertionError(
+ f'Circle size scales improperly! RTOL: {CIRCLE_RTOL} '
+ 'Metric: radius*pixel_size/focal_length should be equal.')
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene6/test_zoom.py b/apps/CameraITS/tests/scene6/test_zoom.py
index 4457046..89cdae5 100644
--- a/apps/CameraITS/tests/scene6/test_zoom.py
+++ b/apps/CameraITS/tests/scene6/test_zoom.py
@@ -39,6 +39,7 @@
NAME = os.path.splitext(os.path.basename(__file__))[0]
NUM_STEPS = 10
OFFSET_RTOL = 0.15
+OFFSET_RTOL_MIN_FD = 0.30
RADIUS_RTOL = 0.10
RADIUS_RTOL_MIN_FD = 0.15
ZOOM_MAX_THRESH = 10.0
@@ -88,9 +89,9 @@
# determine if minimum focus distance is less than rig depth
if (math.isclose(min_fd, 0.0, rel_tol=1E-6) or # fixed focus
1.0/min_fd < chart_distance_m*MIN_FOCUS_DIST_TOL):
- test_tols[focal_l] = RADIUS_RTOL
+ test_tols[focal_l] = (RADIUS_RTOL, OFFSET_RTOL)
else:
- test_tols[focal_l] = RADIUS_RTOL_MIN_FD
+ test_tols[focal_l] = (RADIUS_RTOL_MIN_FD, OFFSET_RTOL_MIN_FD)
logging.debug('loosening RTOL for cam[%s]: '
'min focus distance too large.', i)
# find intersection of formats for max common format
@@ -233,7 +234,7 @@
cam, props, self.chart_distance, debug)
else:
fl = props['android.lens.info.availableFocalLengths'][0]
- test_tols = {fl: RADIUS_RTOL}
+ test_tols = {fl: (RADIUS_RTOL, OFFSET_RTOL)}
yuv_size = capture_request_utils.get_largest_yuv_format(props)
size = [yuv_size['width'], yuv_size['height']]
logging.debug('capture size: %s', str(size))
@@ -255,22 +256,30 @@
# determine radius tolerance of capture
cap_fl = cap['metadata']['android.lens.focalLength']
- radius_tol = test_tols[cap_fl]
+ radius_tol, offset_tol = test_tols[cap_fl]
# convert to [0, 255] images with unsigned integer
img *= 255
img = img.astype(np.uint8)
# Find the center circle in img
- circle = find_center_circle(
- img, img_name, CIRCLE_COLOR,
- min_area=MIN_AREA_RATIO * size[0] * size[1] * z * z,
- debug=debug)
- if circle_cropped(circle, size):
- logging.debug('zoom %.2f is too large! Skip further captures', z)
- break
+ try:
+ circle = find_center_circle(
+ img, img_name, CIRCLE_COLOR,
+ min_area=MIN_AREA_RATIO * size[0] * size[1] * z * z,
+ debug=debug)
+ if circle_cropped(circle, size):
+ logging.debug('zoom %.2f is too large! Skip further captures', z)
+ break
+ except AssertionError:
+ if z/z_list[0] >= ZOOM_MAX_THRESH:
+ break
+ else:
+ raise AssertionError(
+ f'No circle was detected for zoom ratio <= {ZOOM_MAX_THRESH}. '
+ 'Please take pictures according to instructions carefully!')
test_data[i] = {'z': z, 'circle': circle, 'r_tol': radius_tol,
- 'fl': cap_fl}
+ 'o_tol': offset_tol, 'fl': cap_fl}
# assert some range is tested before circles get too big
zoom_max_thresh = ZOOM_MAX_THRESH
@@ -307,16 +316,16 @@
# check relative offset against init vals w/ no focal length change
if i == 0 or test_data[i-1]['fl'] != data['fl']: # set init values
z_init = float(data['z'])
- offset_init = [data['circle'][0] - size[0]//2,
- data['circle'][1] - size[1]//2]
+ offset_init = [(data['circle'][0] - size[0] // 2),
+ (data['circle'][1] - size[1] // 2)]
else: # check
z_ratio = data['z'] / z_init
offset_rel = (distance(offset_abs[0], offset_abs[1]) / z_ratio /
distance(offset_init[0], offset_init[1]))
logging.debug('offset_rel: %.3f', offset_rel)
- if not math.isclose(offset_rel, 1.0, rel_tol=OFFSET_RTOL):
- raise AssertionError(f"zoom: {data['z']:.2f}, offset(rel): "
- f'{offset_rel:.4f}, RTOL: {OFFSET_RTOL}')
+ if not math.isclose(offset_rel, 1.0, rel_tol=data['o_tol']):
+ raise AssertionError(f"zoom: {data['z']:.2f}, offset(rel to 1): "
+ f"{offset_rel:.4f}, RTOL: {data['o_tol']}")
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS/tests/scene_change/scene_change.png b/apps/CameraITS/tests/scene_change/scene_change.png
deleted file mode 100644
index b9554b3..0000000
--- a/apps/CameraITS/tests/scene_change/scene_change.png
+++ /dev/null
Binary files differ
diff --git a/apps/CameraITS/tests/scene_change/scene_change_0.5x_scaled.png b/apps/CameraITS/tests/scene_change/scene_change_0.5x_scaled.png
deleted file mode 100644
index 92e3444..0000000
--- a/apps/CameraITS/tests/scene_change/scene_change_0.5x_scaled.png
+++ /dev/null
Binary files differ
diff --git a/apps/CameraITS/tests/scene_change/scene_change_0.67x_scaled.png b/apps/CameraITS/tests/scene_change/scene_change_0.67x_scaled.png
deleted file mode 100644
index d8cca5c..0000000
--- a/apps/CameraITS/tests/scene_change/scene_change_0.67x_scaled.png
+++ /dev/null
Binary files differ
diff --git a/apps/CameraITS/tests/scene_change/test_scene_change.py b/apps/CameraITS/tests/scene_change/test_scene_change.py
deleted file mode 100644
index 9a8b262..0000000
--- a/apps/CameraITS/tests/scene_change/test_scene_change.py
+++ /dev/null
@@ -1,243 +0,0 @@
-# Copyright 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Verify that the android.control.afSceneChange asserted on scene change."""
-
-
-import logging
-import multiprocessing
-import os.path
-import time
-
-from mobly import test_runner
-
-import its_base_test
-import camera_properties_utils
-import capture_request_utils
-import image_processing_utils
-import its_session_utils
-import scene_change_utils
-
-_BRIGHT_CHANGE_TOL = 0.2
-_CONTINUOUS_PICTURE_MODE = 4
-_CONVERGED_3A = (2, 2, 2) # (AE, AF, AWB)
-_DELAY_CAPTURE = 1.5 # Delay in first capture to sync events (sec).
-_DELAY_DISPLAY = 3.0 # Time when display turns OFF (sec).
-_FPS = 30 # Frames Per Second
-_M_TO_CM = 100
-_NAME = os.path.splitext(os.path.basename(__file__))[0]
-_NSEC_TO_MSEC = 1E-6
-_NUM_TRIES = 6
-_NUM_FRAMES = 50
-_PATCH_H = 0.1 # Center 10%.
-_PATCH_W = 0.1
-_PATCH_X = 0.5 - _PATCH_W/2
-_PATCH_Y = 0.5 - _PATCH_H/2
-_RGB_G_CH = 1
-_SCENE_CHANGE_FLAG_TRUE = 1
-_VALID_SCENE_CHANGE_VALS = (0, 1)
-_VGA_W, _VGA_H = 640, 480
-
-
-def find_3a_converged_frame(cap_data):
- converged_frame = -1
- for i, cap in enumerate(cap_data):
- if cap['3a_state'] == _CONVERGED_3A:
- converged_frame = i
- break
- logging.debug('Frame index where 3A converges: %d', converged_frame)
- return converged_frame
-
-
-def determine_if_scene_changed(cap_data, converged_frame):
- """Determine if the scene has changed during captures.
-
- Args:
- cap_data: Camera capture object.
- converged_frame: Integer indicating when 3A converged.
-
- Returns:
- A 2-tuple of booleans where the first is for AF scene change flag asserted
- and the second is for whether brightness in images changed.
- """
- scene_change_flag = False
- bright_change_flag = False
- start_frame_brightness = cap_data[0]['avg']
- for i in range(converged_frame, len(cap_data)):
- if cap_data[i]['avg'] <= (
- start_frame_brightness * (1.0 - _BRIGHT_CHANGE_TOL)):
- bright_change_flag = True
- if cap_data[i]['flag'] == _SCENE_CHANGE_FLAG_TRUE:
- scene_change_flag = True
- return scene_change_flag, bright_change_flag
-
-
-def toggle_screen(tablet, delay=1):
- """Sets the chart host screen display level .
-
- Args:
- tablet: Object for screen tablet.
- delay: Float value for time delay. Default is 1 second.
- """
- t0 = time.time()
- if delay >= 0:
- time.sleep(delay)
- else:
- raise ValueError(f'Screen toggle time shifted to {delay} w/o scene change. '
- 'Tablet does not appear to be toggling. Check setup.')
- tablet.adb.shell('input keyevent KEYCODE_POWER')
- t = time.time() - t0
- logging.debug('Toggling display at %.3f.', t)
-
-
-def capture_frames(cam, delay, burst, log_path):
- """Capture NUM_FRAMES frames and log metadata.
-
- 3A state information:
- AE_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED,
- 4: FLASH_REQ, 5: PRECAPTURE}
- AF_STATES: {0: INACTIVE, 1: PASSIVE_SCAN, 2: PASSIVE_FOCUSED,
- 3: ACTIVE_SCAN, 4: FOCUS_LOCKED, 5: NOT_FOCUSED_LOCKED,
- 6: PASSIVE_UNFOCUSED}
- AWB_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED}
-
- Args:
- cam: Camera object.
- delay: Float value for time delay in seconds.
- burst: Integer number of burst index.
- log_path: String location to save images.
- Returns:
- cap_data_list. List of dicts for each capture.
- """
- cap_data_list = []
- req = capture_request_utils.auto_capture_request()
- req['android.control.afMode'] = _CONTINUOUS_PICTURE_MODE
- fmt = {'format': 'yuv', 'width': _VGA_W, 'height': _VGA_H}
- t0 = time.time()
- time.sleep(delay)
- logging.debug('cap event start: %.6f', time.time() - t0)
- caps = cam.do_capture([req]*_NUM_FRAMES, fmt)
- logging.debug('cap event stop: %.6f', time.time() - t0)
-
- # Extract frame metadata.
- for i, cap in enumerate(caps):
- cap_data = {}
- exp = cap['metadata']['android.sensor.exposureTime'] * _NSEC_TO_MSEC
- iso = cap['metadata']['android.sensor.sensitivity']
- focal_length = cap['metadata']['android.lens.focalLength']
- ae_state = cap['metadata']['android.control.aeState']
- af_state = cap['metadata']['android.control.afState']
- awb_state = cap['metadata']['android.control.awbState']
- if focal_length:
- fl_str = str(round(_M_TO_CM/focal_length, 2)) + 'cm'
- else:
- fl_str = 'infinity'
- flag = cap['metadata']['android.control.afSceneChange']
- if flag not in _VALID_SCENE_CHANGE_VALS:
- raise AssertionError(f'afSceneChange not a valid value: {flag}.')
- img = image_processing_utils.convert_capture_to_rgb_image(cap)
- image_processing_utils.write_image(
- img, '%s_%d_%d.jpg' % (os.path.join(log_path, _NAME), burst, i))
- patch = image_processing_utils.get_image_patch(
- img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
- green_avg = image_processing_utils.compute_image_means(patch)[_RGB_G_CH]
- logging.debug(
- '%d, iso: %d, exp: %.2fms, fd: %s, avg: %.3f, 3A: [%d,%d,%d], flag: %d',
- i, iso, exp, fl_str, green_avg, ae_state, af_state, awb_state, flag)
- cap_data['3a_state'] = (ae_state, af_state, awb_state)
- cap_data['avg'] = green_avg
- cap_data['flag'] = flag
- cap_data_list.append(cap_data)
- return cap_data_list
-
-
-class SceneChangeTest(its_base_test.ItsBaseTest):
- """Tests that AF scene change detected metadata changes for scene change.
-
- Confirm android.control.afSceneChangeDetected is asserted when scene changes.
-
- Does continuous capture with face scene during scene change. With no scene
- change, behavior should be similar to scene2_b/test_continuous_picture.
- Scene change is modeled with scene tablet powered down during continuous
- capture. If tablet does not exist, scene change can be modeled with hand wave
- in front of camera.
-
- Depending on scene brightness changes and scene change flag assertions during
- test, adjust tablet timing to move scene change to appropriate timing for
- test.
- """
-
- def test_scene_change(self):
- logging.debug('Starting %s', _NAME)
- with its_session_utils.ItsSession(
- device_id=self.dut.serial,
- camera_id=self.camera_id,
- hidden_physical_id=self.hidden_physical_id) as cam:
- props = cam.get_camera_properties()
- props = cam.override_with_hidden_physical_camera_props(props)
- log_path = self.log_path
- tablet = self.tablet
-
- # Check SKIP conditions.
- camera_properties_utils.skip_unless(
- camera_properties_utils.continuous_picture(props) and
- camera_properties_utils.af_scene_change(props))
-
- # Load chart for scene.
- its_session_utils.load_scene(
- cam, props, self.scene, tablet, self.chart_distance)
-
- # Do captures with scene change.
- tablet_level = int(self.tablet_screen_brightness)
- logging.debug('Tablet brightness: %d', tablet_level)
- scene_change_delay = _DELAY_DISPLAY
- cam.do_3a() # Do 3A up front to settle camera.
- for burst in range(_NUM_TRIES):
- logging.debug('burst number: %d', burst)
- # Create scene change by turning off chart display & capture frames
- if tablet:
- multiprocessing.Process(name='p1', target=toggle_screen,
- args=(tablet, scene_change_delay,)).start()
- else:
- print('Wave hand in front of camera to create scene change.')
- cap_data = capture_frames(cam, _DELAY_CAPTURE, burst, log_path)
-
- # Find frame where 3A converges and final brightness.
- converged_frame = find_3a_converged_frame(cap_data)
- converged_flag = True if converged_frame != -1 else False
- bright_final = cap_data[_NUM_FRAMES - 1]['avg']
-
- # Determine if scene changed.
- scene_change_flag, bright_change_flag = determine_if_scene_changed(
- cap_data, converged_frame)
-
- # Adjust timing based on captured frames and scene change flags.
- timing_adjustment = scene_change_utils.calc_timing_adjustment(
- converged_flag, scene_change_flag, bright_change_flag, bright_final)
- if timing_adjustment == scene_change_utils.SCENE_CHANGE_PASS_CODE:
- break
- elif timing_adjustment == scene_change_utils.SCENE_CHANGE_FAIL_CODE:
- raise AssertionError('Test fails. Check logging.error.')
- else:
- if burst == _NUM_TRIES-1: # FAIL out after NUM_TRIES.
- raise AssertionError(f'No scene change in {_NUM_TRIES}x tries.')
- else:
- scene_change_delay += timing_adjustment / _FPS
-
- if tablet:
- logging.debug('Turning screen back ON.')
- toggle_screen(tablet)
-
-
-if __name__ == '__main__':
- test_runner.main()
diff --git a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
index 4d5a36f..0b2bbcc 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -340,7 +340,7 @@
gframe0, mask=pre_mask, **_CV2_FEATURE_PARAMS_PREMASK)
num_features = len(p0_filtered)
if num_features < _FEATURE_PTS_MIN:
- for pt in p0_filtered:
+ for pt in np.rint(p0_filtered).astype(int):
x, y = pt[0][0], pt[0][1]
cv2.circle(frames[j], (x, y), 3, (100, 255, 255), -1)
image_processing_utils.write_image(
@@ -369,7 +369,7 @@
# Save debug visualization of features that are being
# tracked in the first frame.
frame = frames[j]
- for x, y in p0_filtered[st == 1]:
+ for x, y in np.rint(p0_filtered[st == 1]).astype(int):
cv2.circle(frame, (x, y), 3, (100, 255, 255), -1)
image_processing_utils.write_image(
frame, f'{file_name_stem}_features{j+_START_FRAME:03d}.png')
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index d5d3b06..c285a69 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -16,6 +16,7 @@
import logging
import os
import os.path
+import re
import subprocess
import sys
import tempfile
@@ -41,6 +42,7 @@
RESULT_FAIL = 'FAIL'
RESULT_NOT_EXECUTED = 'NOT_EXECUTED'
RESULT_KEY = 'result'
+METRICS_KEY = 'mpc_metrics'
SUMMARY_KEY = 'summary'
RESULT_VALUES = {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}
ITS_TEST_ACTIVITY = 'com.android.cts.verifier/.camera.its.ItsTestActivity'
@@ -61,13 +63,13 @@
_ALL_SCENES = [
'scene0', 'scene1_1', 'scene1_2', 'scene2_a', 'scene2_b', 'scene2_c',
'scene2_d', 'scene2_e', 'scene3', 'scene4', 'scene5', 'scene6',
- 'sensor_fusion', 'scene_change'
+ 'sensor_fusion'
]
# Scenes that can be automated through tablet display
_AUTO_SCENES = [
'scene0', 'scene1_1', 'scene1_2', 'scene2_a', 'scene2_b', 'scene2_c',
- 'scene2_d', 'scene2_e', 'scene3', 'scene4', 'scene6', 'scene_change'
+ 'scene2_d', 'scene2_e', 'scene3', 'scene4', 'scene6'
]
# Scenes that are logically grouped and can be called as group
@@ -101,7 +103,6 @@
'See tests/sensor_fusion/SensorFusion.pdf for detailed '
'instructions.\nNote that this test will be skipped '
'on devices not supporting REALTIME camera timestamp.',
- 'scene_change': 'The picture with 3 faces in tests/scene2_e/scene2_e.png',
}
@@ -127,7 +128,6 @@
'test_yuv_plus_raw',
],
'scene2_a': [
- 'test_faces',
'test_num_faces',
],
'scene4': [
@@ -251,7 +251,7 @@
config_file_contents: a dict read from config.yml
"""
with open(CONFIG_FILE) as file:
- config_file_contents = yaml.load(file, yaml.FullLoader)
+ config_file_contents = yaml.safe_load(file)
return config_file_contents
@@ -290,9 +290,9 @@
for device_dict in android_device_contents.get('AndroidDevice'):
for _, label in device_dict.items():
if label == 'tablet':
- tablet_device_id = device_dict.get('serial')
+ tablet_device_id = str(device_dict.get('serial'))
if label == 'dut':
- dut_device_id = device_dict.get('serial')
+ dut_device_id = str(device_dict.get('serial'))
if device == 'tablet':
return tablet_device_id
else:
@@ -452,6 +452,7 @@
for s in per_camera_scenes:
test_params_content['scene'] = s
results[s]['TEST_STATUS'] = []
+ results[s][METRICS_KEY] = []
# unit is millisecond for execution time record in CtsVerifier
scene_start_time = int(round(time.time() * 1000))
@@ -527,14 +528,28 @@
test_failed = False
test_skipped = False
test_not_yet_mandated = False
- line = file.read()
- if 'Test skipped' in line:
+ test_mpc_req = ""
+ content = file.read()
+
+ # Find media performance class logging
+ lines = content.splitlines()
+ for one_line in lines:
+ # regular expression pattern must match
+ # MPC12_CAMERA_LAUNCH_PATTERN or MPC12_JPEG_CAPTURE_PATTERN in
+ # ItsTestActivity.java.
+ mpc_string_match = re.search(
+ '^(1080p_jpeg_capture_time_ms:|camera_launch_time_ms:)', one_line)
+ if mpc_string_match:
+ test_mpc_req = one_line
+ break
+
+ if 'Test skipped' in content:
return_string = 'SKIP '
num_skip += 1
test_skipped = True
break
- if 'Not yet mandated test' in line:
+ if 'Not yet mandated test' in content:
return_string = 'FAIL*'
num_not_mandated_fail += 1
test_not_yet_mandated = True
@@ -547,7 +562,7 @@
if test_code == 1 and not test_not_yet_mandated:
return_string = 'FAIL '
- if 'Problem with socket' in line and num_try != NUM_TRIES-1:
+ if 'Problem with socket' in content and num_try != NUM_TRIES-1:
logging.info('Retry %s/%s', s, test)
else:
num_fail += 1
@@ -557,6 +572,8 @@
logging.info('%s %s/%s', return_string, s, test)
test_name = test.split('/')[-1].split('.')[0]
results[s]['TEST_STATUS'].append({'test':test_name,'status':return_string.strip()})
+ if test_mpc_req:
+ results[s][METRICS_KEY].append(test_mpc_req)
msg_short = '%s %s' % (return_string, test)
scene_test_summary += msg_short + '\n'
diff --git a/apps/CameraITS/utils/image_processing_utils.py b/apps/CameraITS/utils/image_processing_utils.py
index a364b53..4f87ae8 100644
--- a/apps/CameraITS/utils/image_processing_utils.py
+++ b/apps/CameraITS/utils/image_processing_utils.py
@@ -45,18 +45,18 @@
TEST_IMG_DIR = os.path.join(os.environ['CAMERA_ITS_TOP'], 'test_images')
-# pylint: disable=unused-argument
+def assert_props_is_not_none(props):
+ if not props:
+ raise AssertionError('props is None')
+
+
def convert_capture_to_rgb_image(cap,
- ccm_yuv_to_rgb=DEFAULT_YUV_TO_RGB_CCM,
- yuv_off=DEFAULT_YUV_OFFSETS,
props=None,
apply_ccm_raw_to_rgb=True):
"""Convert a captured image object to a RGB image.
Args:
cap: A capture object as returned by its_session_utils.do_capture.
- ccm_yuv_to_rgb: (Optional) the 3x3 CCM to convert from YUV to RGB.
- yuv_off: (Optional) offsets to subtract from each of Y,U,V values.
props: (Optional) camera properties object (of static values);
required for processing raw images.
apply_ccm_raw_to_rgb: (Optional) boolean to apply color correction matrix.
@@ -67,11 +67,11 @@
w = cap['width']
h = cap['height']
if cap['format'] == 'raw10':
- assert props is not None
+ assert_props_is_not_none(props)
cap = unpack_raw10_capture(cap)
if cap['format'] == 'raw12':
- assert props is not None
+ assert_props_is_not_none(props)
cap = unpack_raw12_capture(cap)
if cap['format'] == 'yuv':
@@ -82,7 +82,7 @@
elif cap['format'] == 'jpeg':
return decompress_jpeg_to_rgb_image(cap['data'])
elif cap['format'] == 'raw' or cap['format'] == 'rawStats':
- assert props is not None
+ assert_props_is_not_none(props)
r, gr, gb, b = convert_capture_to_planes(cap, props)
return convert_raw_to_rgb_image(
r, gr, gb, b, props, cap['metadata'], apply_ccm_raw_to_rgb)
@@ -276,10 +276,10 @@
w = cap['width']
h = cap['height']
if cap['format'] == 'raw10':
- assert props is not None
+ assert_props_is_not_none(props)
cap = unpack_raw10_capture(cap)
if cap['format'] == 'raw12':
- assert props is not None
+ assert_props_is_not_none(props)
cap = unpack_raw12_capture(cap)
if cap['format'] == 'yuv':
y = cap['data'][0:w * h]
@@ -293,7 +293,7 @@
return (rgb[::3].reshape(h, w, 1), rgb[1::3].reshape(h, w, 1),
rgb[2::3].reshape(h, w, 1))
elif cap['format'] == 'raw':
- assert props is not None
+ assert_props_is_not_none(props)
white_level = float(props['android.sensor.info.whiteLevel'])
img = numpy.ndarray(
shape=(h * w,), dtype='<u2', buffer=cap['data'][0:w * h * 2])
@@ -314,10 +314,14 @@
'right'] - xcrop
hcrop = props['android.sensor.info.preCorrectionActiveArraySize'][
'bottom'] - ycrop
- assert wfull >= wcrop >= 0
- assert hfull >= hcrop >= 0
- assert wfull - wcrop >= xcrop >= 0
- assert hfull - hcrop >= ycrop >= 0
+ if not wfull >= wcrop >= 0:
+ raise AssertionError(f'wcrop: {wcrop} not in wfull: {wfull}')
+ if not hfull >= hcrop >= 0:
+ raise AssertionError(f'hcrop: {hcrop} not in hfull: {hfull}')
+ if not wfull - wcrop >= xcrop >= 0:
+ raise AssertionError(f'xcrop: {xcrop} not in wfull-crop: {wfull-wcrop}')
+ if not hfull - hcrop >= ycrop >= 0:
+ raise AssertionError(f'ycrop: {ycrop} not in hfull-crop: {hfull-hcrop}')
if w == wfull and h == hfull:
# Crop needed; extract the center region.
img = img[ycrop:ycrop + hcrop, xcrop:xcrop + wcrop]
@@ -339,7 +343,7 @@
idxs = get_canonical_cfa_order(props)
return [imgs[i] for i in idxs]
elif cap['format'] == 'rawStats':
- assert props is not None
+ assert_props_is_not_none(props)
white_level = float(props['android.sensor.info.whiteLevel'])
# pylint: disable=unused-variable
mean_image, var_image = unpack_rawstats_capture(cap)
@@ -405,7 +409,7 @@
RGB float-3 image array, with pixel values in [0.0, 1.0]
"""
# Values required for the RAW to RGB conversion.
- assert props is not None
+ assert_props_is_not_none(props)
white_level = float(props['android.sensor.info.whiteLevel'])
black_levels = props['android.sensor.blackLevelPattern']
gains = cap_res['android.colorCorrection.gains']
@@ -613,7 +617,8 @@
Tuple (mean_image var_image) of float-4 images, with non-normalized
pixel values computed from the RAW16 images on the device
"""
- assert cap['format'] == 'rawStats'
+ if cap['format'] != 'rawStats':
+ raise AssertionError(f"Unpack fmt != rawStats: {cap['format']}")
w = cap['width']
h = cap['height']
img = numpy.ndarray(shape=(2 * h * w * 4,), dtype='<f', buffer=cap['data'])
@@ -691,7 +696,8 @@
Larger value means the image is sharper.
"""
chans = img.shape[2]
- assert chans == 1 or chans == 3
+ if chans != 1 and chans != 3:
+ raise AssertionError(f'Not RGB or MONO image! depth: {chans}')
if chans == 1:
luma = img[:, :, 0]
else:
@@ -741,7 +747,9 @@
Returns:
2-D grayscale image
"""
- assert img.shape[2] == 3, 'Not an RGB image'
+ chans = img.shape[2]
+ if chans != 3:
+ raise AssertionError(f'Not an RGB image! Depth: {chans}')
return 0.299*img[:, :, 0] + 0.587*img[:, :, 1] + 0.114*img[:, :, 2]
@@ -770,22 +778,6 @@
return img_out
-def chart_located_per_argv(chart_loc_arg):
- """Determine if chart already located outside of test.
-
- If chart info provided, return location and size. If not, return None.
- Args:
- chart_loc_arg: chart_loc arg value.
-
- 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'
- """
- if chart_loc_arg:
- return map(float, chart_loc_arg)
- return None, None, None, None, None
-
-
def stationary_lens_cap(cam, req, fmt):
"""Take up to NUM_TRYS caps and save the 1st one with lens stationary.
@@ -823,7 +815,10 @@
rms_diff
"""
len_rgb_x = len(rgb_x)
- assert len(rgb_y) == len_rgb_x, 'The images have different number of planes.'
+ len_rgb_y = len(rgb_y)
+ if len_rgb_y != len_rgb_x:
+ raise AssertionError('RGB images have different number of planes! '
+ f'x: {len_rgb_x}, y: {len_rgb_y}')
return math.sqrt(sum([pow(rgb_x[i] - rgb_y[i], 2.0)
for i in range(len_rgb_x)]) / len_rgb_x)
diff --git a/apps/CameraITS/utils/its_session_utils.py b/apps/CameraITS/utils/its_session_utils.py
index 4c47388..67f0626 100644
--- a/apps/CameraITS/utils/its_session_utils.py
+++ b/apps/CameraITS/utils/its_session_utils.py
@@ -876,6 +876,7 @@
lock_awb=False,
get_results=False,
ev_comp=0,
+ auto_flash=False,
mono_camera=False):
"""Perform a 3A operation on the device.
@@ -896,6 +897,7 @@
lock_awb: Request AWB lock after convergence, and wait for it.
get_results: Return the 3A results from this function.
ev_comp: An EV compensation value to use when running AE.
+ auto_flash: AE control boolean to enable auto flash.
mono_camera: Boolean for monochrome camera.
Region format in args:
@@ -930,6 +932,8 @@
cmd['awbLock'] = True
if ev_comp != 0:
cmd['evComp'] = ev_comp
+ if auto_flash:
+ cmd['autoFlash'] = True
if self._hidden_physical_id:
cmd['physicalId'] = self._hidden_physical_id
self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
@@ -1093,8 +1097,8 @@
' support')
return data['strValue'] == 'true'
- def is_performance_class_primary_camera(self):
- """Query whether the camera device is an R or S performance class primary camera.
+ def is_primary_camera(self):
+ """Query whether the camera device is a primary rear/front camera.
A primary rear/front facing camera is a camera device with the lowest
camera Id for that facing.
@@ -1103,14 +1107,28 @@
Boolean
"""
cmd = {}
- cmd['cmdName'] = 'isPerformanceClassPrimaryCamera'
+ cmd['cmdName'] = 'isPrimaryCamera'
cmd['cameraId'] = self._camera_id
self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
data, _ = self.__read_response_from_socket()
- if data['tag'] != 'performanceClassPrimaryCamera':
- raise error_util.CameraItsError('Failed to query performance class '
- 'primary camera')
+ if data['tag'] != 'primaryCamera':
+ raise error_util.CameraItsError('Failed to query primary camera')
+ return data['strValue'] == 'true'
+
+ def is_performance_class(self):
+ """Query whether the mobile device is an R or S performance class device.
+
+ Returns:
+ Boolean
+ """
+ cmd = {}
+ cmd['cmdName'] = 'isPerformanceClass'
+ self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+
+ data, _ = self.__read_response_from_socket()
+ if data['tag'] != 'performanceClass':
+ raise error_util.CameraItsError('Failed to query performance class')
return data['strValue'] == 'true'
def measure_camera_launch_ms(self):
diff --git a/apps/CameraITS/utils/lighting_control_utils.py b/apps/CameraITS/utils/lighting_control_utils.py
new file mode 100644
index 0000000..a78c658
--- /dev/null
+++ b/apps/CameraITS/utils/lighting_control_utils.py
@@ -0,0 +1,100 @@
+# Copyright 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Utility functions for sensor_fusion hardware rig."""
+
+
+import logging
+import struct
+import time
+import sensor_fusion_utils
+
+# Constants for Arduino
+ARDUINO_BRIGHTNESS_MAX = 255
+ARDUINO_BRIGHTNESS_MIN = 0
+ARDUINO_LIGHT_START_BYTE = 254
+
+
+def set_light_brightness(ch, brightness, serial_port, delay=0):
+ """Turn on light to specified brightness.
+
+ Args:
+ ch: str; light to turn on in ARDUINO_VALID_CH
+ brightness: int value of brightness between 0 and 255.
+ serial_port: object; serial port
+ delay: int; time in seconds
+ """
+ if brightness < ARDUINO_BRIGHTNESS_MIN:
+ logging.debug('Brightness must be >= %d.', ARDUINO_BRIGHTNESS_MIN)
+ brightness = ARDUINO_BRIGHTNESS_MIN
+ elif brightness > ARDUINO_BRIGHTNESS_MAX:
+ logging.debug('Brightness must be <= %d.', ARDUINO_BRIGHTNESS_MAX)
+ brightness = ARDUINO_BRIGHTNESS_MAX
+
+ cmd = [struct.pack('B', i) for i in [
+ ARDUINO_LIGHT_START_BYTE, int(ch), brightness]]
+ sensor_fusion_utils.arduino_send_cmd(serial_port, cmd)
+ time.sleep(delay)
+
+
+def lighting_control(lighting_cntl, lighting_ch):
+ """Establish communication with lighting controller.
+
+ lighting_ch is hard wired and must be determined from physical setup.
+
+ First initialize the port and send a test string defined by ARDUINO_TEST_CMD
+ to establish communications.
+
+ Args:
+ lighting_cntl: str to identify 'arduino' controller.
+ lighting_ch: str to identify lighting channel number.
+ Returns:
+ serial port pointer
+ """
+
+ logging.debug('Controller: %s, ch: %s', lighting_cntl, lighting_ch)
+ if lighting_cntl.lower() == 'arduino':
+ # identify port
+ arduino_serial_port = sensor_fusion_utils.serial_port_def('arduino')
+
+ # send test cmd to Arduino until cmd returns properly
+ sensor_fusion_utils.establish_serial_comm(arduino_serial_port)
+
+ # return serial port
+ return arduino_serial_port
+
+ else:
+ logging.debug('No lighting control: need to control lights manually.')
+ return None
+
+
+def set_lighting_state(arduino_serial_port, lighting_ch, state):
+ """Turn lights ON in test rig.
+
+ Args:
+ arduino_serial_port: serial port object
+ lighting_ch: str for lighting channel
+ state: str 'ON/OFF'
+ """
+ if state == 'ON':
+ level = 255
+ elif state == 'OFF':
+ level = 0
+ else:
+ raise AssertionError(f'Lighting state not defined correctly: {state}')
+
+ if arduino_serial_port:
+ set_light_brightness(lighting_ch, level, arduino_serial_port, delay=1)
+ else:
+ _ = input(f'Turn {state} lights in rig and hit ENTER to continue.')
+
diff --git a/apps/CameraITS/utils/opencv_processing_utils.py b/apps/CameraITS/utils/opencv_processing_utils.py
index 8c6a3f7..d6f10b6 100644
--- a/apps/CameraITS/utils/opencv_processing_utils.py
+++ b/apps/CameraITS/utils/opencv_processing_utils.py
@@ -63,7 +63,7 @@
SCALE_TELE25_IN_RFOV_BOX = 0.33
SQUARE_AREA_MIN_REL = 0.05 # Minimum size for square relative to image area
-SQUARE_TOL = 0.1 # Square W vs H mismatch RTOL
+SQUARE_TOL = 0.05 # Square W vs H mismatch RTOL
VGA_HEIGHT = 480
VGA_WIDTH = 640
@@ -139,7 +139,6 @@
cam,
props,
log_path,
- chart_loc=None,
chart_file=None,
height=None,
distance=None,
@@ -152,7 +151,6 @@
cam: open ITS session
props: camera properties object
log_path: log path to store the captured images.
- chart_loc: chart locator arg.
chart_file: str; absolute path to png file of chart
height: float; height in cm of displayed chart
distance: float; distance in cm from camera of displayed chart
@@ -166,10 +164,7 @@
self._scale_start = scale_start or CHART_SCALE_START
self._scale_stop = scale_stop or CHART_SCALE_STOP
self._scale_step = scale_step or CHART_SCALE_STEP
- self.xnorm, self.ynorm, self.wnorm, self.hnorm, self.scale = (
- image_processing_utils.chart_located_per_argv(chart_loc))
- if not self.xnorm:
- self.locate(cam, props, log_path)
+ self.locate(cam, props, log_path)
def _set_scale_factors_to_one(self):
"""Set scale factors to 1.0 for skipped tests."""
diff --git a/apps/CameraITS/utils/scene_change_utils.py b/apps/CameraITS/utils/scene_change_utils.py
deleted file mode 100644
index fca397b..0000000
--- a/apps/CameraITS/utils/scene_change_utils.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# Copyright 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Utility functions for scene change test."""
-
-
-import logging
-import unittest
-
-_DARK_SCENE_THRESH = 0.2
-_FPS = 30 # Frames Per Second
-_FRAME_SHIFT_SMALL = 5 # Num of frames to shift if scene or brightness change.
-_FRAME_SHIFT_LARGE = 30 # Num of frames to shift if no change in capture.
-SCENE_CHANGE_FAIL_CODE = -1001
-SCENE_CHANGE_PASS_CODE = 1001
-
-
-def calc_timing_adjustment(converged, scene_change_flag,
- bright_change_flag, bright_final):
- """Calculate timing adjustment based on converged frame and flags.
-
- Args:
- converged: Boolean on whether 3A converged or not.
- scene_change_flag: Boolean for if afSceneChanged triggered.
- bright_change_flag: Boolean for if image brightness changes.
- bright_final: Float for average value of center patch of final frame.
- Returns:
- scene_change_timing_shift: Timing shift in frames.
-
- Does timing adjustment based on input values from captured frames.
- Truth table for 3A frame, Change flag, Bright flag, Last frame brightness
- 3, C, B, L
- 1, 1, 1, X --> PASS: 3A settled, scene and brightness change
- 1, 1, 0, X --> FAIL: 3A settled, scene change, but no brightness change
- 1, 0, 1, X --> shift FRAME_SHIFT_SMALL earlier
- 1, 0, 0, 1 --> shift FRAME_SHIFT_LARGE earlier
- 1, 0, 0, 0 --> shift FRAME_SHIFT_LARGE later
- 0, X, 1, X --> shift FRAME_SHIFT_SMALL later
- 0, X, 0, X --> FAIL: Check results of scene2/test_continuous_picture.
- Note: these values have been found empirically for 4 different phone
- models and 8 cameras. It is possible they may need to be tweaked as
- more phone models become available.
- """
- if converged: # 3A converges
- if scene_change_flag:
- if bright_change_flag: # scene_change_flag & brightness change --> PASS
- logging.debug('Scene & brightness change: PASS.')
- return SCENE_CHANGE_PASS_CODE
- else: # scene_change_flag & no brightness change --> FAIL
- scene_change_frame_shift = SCENE_CHANGE_FAIL_CODE
- logging.error('Scene change, but no brightness change.')
- else: # No scene change flag: shift timing
- if bright_change_flag:
- scene_change_frame_shift = -1 * _FRAME_SHIFT_SMALL
- logging.debug('No scene change flag, but brightness change.')
- else:
- logging.debug('No scene change flag, no brightness change.')
- if bright_final < _DARK_SCENE_THRESH:
- scene_change_frame_shift = _FRAME_SHIFT_LARGE
- logging.debug('Scene dark entire capture.')
- else:
- scene_change_frame_shift = -1 * _FRAME_SHIFT_LARGE
- logging.debug('Scene light entire capture.')
- else: # 3A does not converge.
- if bright_change_flag:
- scene_change_frame_shift = _FRAME_SHIFT_SMALL
- logging.debug('3A does not converge, but brightness changes.')
- else:
- scene_change_frame_shift = SCENE_CHANGE_FAIL_CODE
- logging.error('3A does not converge, and brightness does not change.')
- if scene_change_frame_shift >= 0:
- logging.debug('Shift +%d frames.', scene_change_frame_shift)
- else:
- logging.debug('Shift %d frames.', scene_change_frame_shift)
- return scene_change_frame_shift
-
-
-class ItsSessionUtilsTests(unittest.TestCase):
- """Unit tests for this module."""
-
- def test_calc_timing_adjustment_shift(self):
- results = {}
- expected_results = {'1111': SCENE_CHANGE_PASS_CODE,
- '1110': SCENE_CHANGE_PASS_CODE,
- '1101': SCENE_CHANGE_FAIL_CODE,
- '1100': SCENE_CHANGE_FAIL_CODE,
- '1011': -1*_FRAME_SHIFT_SMALL,
- '1010': -1*_FRAME_SHIFT_SMALL,
- '1001': -1*_FRAME_SHIFT_LARGE,
- '1000': _FRAME_SHIFT_LARGE,
- '0111': _FRAME_SHIFT_SMALL,
- '0110': _FRAME_SHIFT_SMALL,
- '0101': SCENE_CHANGE_FAIL_CODE,
- '0100': SCENE_CHANGE_FAIL_CODE,
- '0011': _FRAME_SHIFT_SMALL,
- '0010': _FRAME_SHIFT_SMALL,
- '0001': SCENE_CHANGE_FAIL_CODE,
- '0000': SCENE_CHANGE_FAIL_CODE,
- }
- converged_list = [1, 0]
- scene_change_flag_list = [1, 0]
- bright_change_flag_list = [1, 0]
- bright_final_list = [1, 0]
- for converged in converged_list:
- for scene_flag in scene_change_flag_list:
- for bright_flag in bright_change_flag_list:
- for bright_final in bright_final_list:
- key = f'{converged}{scene_flag}{bright_flag}{bright_final}'
- results[key] = calc_timing_adjustment(converged, scene_flag,
- bright_flag, bright_final)
- self.assertEqual(results, expected_results)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/apps/CtsVerifier/Android.bp b/apps/CtsVerifier/Android.bp
index 628f234..7a4b2ac 100644
--- a/apps/CtsVerifier/Android.bp
+++ b/apps/CtsVerifier/Android.bp
@@ -91,7 +91,7 @@
"DpmWrapper"
],
- libs: ["telephony-common"] + ["android.test.runner.stubs"] + ["android.test.base.stubs"] + ["android.test.mock.stubs"] + ["android.car"] + ["voip-common"] + ["truth-prebuilt"],
+ libs: ["telephony-common"] + ["android.test.runner.stubs"] + ["android.test.base.stubs"] + ["android.test.mock.stubs"] + ["android.car-test-stubs"] + ["voip-common"] + ["truth-prebuilt"],
platform_apis: true,
@@ -162,6 +162,7 @@
filegroup {
name: "other_required_apps",
srcs: [
+ ":CtsTileServiceApp",
":CtsVerifierUSBCompanion",
":CtsVpnFirewallAppApi23",
":CtsVpnFirewallAppApi24",
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 04010d5..77ff045 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -48,6 +48,7 @@
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+ <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_PASSWORD_COMPLEXITY" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/>
@@ -76,6 +77,7 @@
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
@@ -3153,11 +3155,26 @@
android:value="multi_display_mode" />
</activity>
+ <activity android:name=".qstiles.TileServiceRequestVerifierActivity"
+ android:exported="true"
+ android:label="@string/tiles_request_test">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_tiles" />
+ <meta-data android:name="test_excluded_features"
+ android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
+ <meta-data android:name="test_required_configs" android:value="config_quick_settings_supported" />
+ <meta-data android:name="display_mode"
+ android:value="multi_display_mode" />
+ </activity>
+
<service android:name=".qstiles.MockTileService"
android:icon="@android:drawable/ic_dialog_alert"
android:label="@string/tile_service_name"
android:enabled="false"
- android:exported="true"
+ android:exported="true"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
@@ -3703,6 +3720,20 @@
android:value="single_display_mode" />
</activity>
+ <activity android:name=".wifiaware.DataPathForceChannelSetupSubscribeTestActivity"
+ android:label="@string/aware_data_path_force_channel_setup_subscribe"
+ android:configChanges="keyboardHidden|orientation|screenSize" >
+ <meta-data android:name="display_mode"
+ android:value="single_display_mode" />
+ </activity>
+
+ <activity android:name=".wifiaware.DataPathForceChannelSetupPublishTestActivity"
+ android:label="@string/aware_data_path_force_channel_setup_publish"
+ android:configChanges="keyboardHidden|orientation|screenSize" >
+ <meta-data android:name="display_mode"
+ android:value="single_display_mode" />
+ </activity>
+
<activity-alias android:name=".CtsVerifierActivity" android:label="@string/app_name"
android:exported="true"
android:targetActivity=".TestListActivity">
diff --git a/apps/CtsVerifier/res/drawable/display_cutout_test_button.xml b/apps/CtsVerifier/res/drawable/display_cutout_test_button.xml
index 18eef3e..b40c71d 100644
--- a/apps/CtsVerifier/res/drawable/display_cutout_test_button.xml
+++ b/apps/CtsVerifier/res/drawable/display_cutout_test_button.xml
@@ -27,6 +27,16 @@
<corners android:radius="4dp" />
</shape>
</item>
+ <item android:state_focused="true">
+ <shape>
+ <solid android:color="#ffbfbfbf" />
+ <padding android:left="4dp"
+ android:top="4dp"
+ android:right="4dp"
+ android:bottom="4dp" />
+ <corners android:radius="4dp" />
+ </shape>
+ </item>
<item>
<shape>
<solid android:color="#ff7f7f7f" />
diff --git a/apps/CtsVerifier/res/layout/audio_dev_notify.xml b/apps/CtsVerifier/res/layout/audio_dev_notify.xml
index 6fa178d..e8f49ce 100644
--- a/apps/CtsVerifier/res/layout/audio_dev_notify.xml
+++ b/apps/CtsVerifier/res/layout/audio_dev_notify.xml
@@ -38,12 +38,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
- <Button
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:id="@+id/audio_dev_notification_connect_clearmsgs_btn"
- android:text="@string/audio_dev_notification_clearmsgs"/>
-
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/apps/CtsVerifier/res/layout/camera_fov_calibration_photo_capture.xml b/apps/CtsVerifier/res/layout/camera_fov_calibration_photo_capture.xml
index c142b15..0150ac5 100644
--- a/apps/CtsVerifier/res/layout/camera_fov_calibration_photo_capture.xml
+++ b/apps/CtsVerifier/res/layout/camera_fov_calibration_photo_capture.xml
@@ -4,7 +4,7 @@
android:layout_height="match_parent"
tools:context=".PhotoCaptureActivity" >
- <SurfaceView
+ <TextureView
android:id="@+id/camera_fov_camera_preview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/apps/CtsVerifier/res/layout/tiles_item.xml b/apps/CtsVerifier/res/layout/tiles_item.xml
index f2adaa4..9496a70 100644
--- a/apps/CtsVerifier/res/layout/tiles_item.xml
+++ b/apps/CtsVerifier/res/layout/tiles_item.xml
@@ -38,10 +38,25 @@
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/tiles_status"/>
+ <Button
+ android:id="@+id/tiles_action_request"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layout_toRightOf="@id/tiles_status"
+ android:layout_below="@id/tiles_instructions"
+ android:layout_alignParentRight="true"
+ android:layout_marginLeft="20dip"
+ android:layout_marginRight="20dip"
+ android:onClick="actionPressed"
+ android:text="Start request"
+ android:enabled="false"
+ />
+
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_below="@id/tiles_instructions"
+ android:layout_below="@id/tiles_action_request"
android:layout_toRightOf="@id/tiles_status"
android:layout_alignParentRight="true">
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 5771068..f645f91 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -2123,6 +2123,7 @@
<string name="aware_dp_ib_open_solicited_accept_any">Data Path: Open: Solicited/Active: accept any peer</string>
<string name="aware_dp_ib_passphrase_solicited_accept_any">Data Path: Passphrase: Solicited/Active: accept any peer</string>
<string name="aware_dp_ib_pmk_solicited_accept_any">Data Path: PMK: Solicited/Active: accept any peer</string>
+ <string name="aware_dp_ib_force_channel_setup">Data Path: force channel setup</string>
<string name="aware_discovery_ranging">Discovery with Ranging</string>
<string name="aware_publish">Publish</string>
<string name="aware_subscribe">Subscribe</string>
@@ -2213,6 +2214,10 @@
<string name="aware_discovery_ranging_publish_info">The publisher is now ready.\n\nOn the other device: start the \'Discovery with Ranging\' / \'Subscribe\' test.</string>
<string name="aware_discovery_ranging_subscribe">Discovery with Ranging: Subscribe</string>
+ <string name="aware_data_path_force_channel_setup_publish">Data Path: force channel setup Publish</string>
+ <string name="aware_data_path_force_channel_setup_publish_info">The publisher is now ready.\n\nOn the other device: start the \'Data Path: force channel setup\' / \'Subscribe\' test.</string>
+ <string name="aware_data_path_force_channel_setup_subscribe">Data Path: force channel setup Subscribe</string>
+
<string name="camera_fov_calibration">Camera FOV Calibration</string>
<string name="camera_fov_calibration_done">Done</string>
<string name="camera_fov_general_settings">General settings</string>
@@ -2384,6 +2389,11 @@
notifications, fully expand the notification display and verify that the \"Person A\"
notification appears before the "\Non-Person Notification\".
If this device does not support conversation notifications or does not group them together, press Pass.</string>
+ <string name="hun_display">If this device supports heads-up notifications,
+ verify that a notification just peeked on screen, displaying all of the following: small
+ icon, large icon, notification title, notification text, and two action buttons.
+ If this device does not support heads-up notifications, press Pass.</string>
+ <string name="action">Action %1$d</string>
<string name="tile_service_name">Tile Service for CTS Verifier</string>
<string name="tiles_test">Tile Service Test</string>
<string name="tiles_info">This test checks that a Tile Service added by a third party
@@ -2396,6 +2406,46 @@
<string name="tiles_in_customizer">Open Quick Settings and click the button to customize Quick
Settings. Check that the Tile Service for CTS Verifier is available to be added</string>
<string name="tiles_removing_tile">Check that Tile Service is disabled</string>
+ <string name="tile_request_service_name">Request Tile Service</string>
+ <string name="tile_request_helper_app_name">CtsTileServiceApp</string>
+ <string name="tiles_request_test">Tile Service Request Test</string>
+ <string name="tiles_request_info">This test checks that a request to add a TileService is
+ shown correctly to the user and the correct answer is relayed to the app.</string>
+ <string name="tiles_request_uninstall">This will verify that the helper app is not installed.
+ If that is correct, the test will pass automatically. If the app is installed, press the button
+ and you will be prompted for confirmation to uninstall the app.</string>
+ <string name="tiles_request_install">You should have received a helper app apk with this CTS
+ Verifier package. Its name is \"CtsTileServiceApp\". Install the test app, by running\n
+ \"adb install /path/to/CtsTileServiceApp.apk\".\n
+ Pass the test if the install was successful. Fail it otherwise.
+ </string>
+ <string name="tiles_request_install_verify">This will automatically verify that the helper app
+ is correctly installed.</string>
+ <string name="tiles_request_tile_not_present">Open Quick Settings and pass this test
+ if there are no tiles named %1$s in any page.</string>
+ <string name="tiles_request_dismissed">After pressing \"Start request\", a dialog will appear
+ requesting to add a quick settings tile. When it appears, dismiss it by tapping outside of
+ it, or going back.</string>
+ <string name="tiles_request_answer_no">After pressing \"Start request\", a dialog will appear
+ requesting to add a quick settings tile. When it appears, tap the option in the dialog to
+ not add the tile.</string>
+ <string name="tiles_request_correct_info">After pressing \"Start request\", a dialog will appear
+ requesting to add a quick settings tile. When it appears, verify the following information
+ in the dialog:\n
+ 1. The name of the app requesting to add the tile is %1$s.\n
+ 2. The label in the tile is %2$s.\n
+After verifying, tap the option in the dialog to not add the tile.\n
+When the buttons are enabled, pass this test if the information in the dialog was correct.
+ </string>
+ <string name="tiles_request_answer_yes">After pressing \"Start request\", a dialog will appear
+ requesting to add a quick settings tile. When it appears, tap the option in the dialog to
+ add the tile.
+ </string>
+ <string name="tiles_request_tile_present">Open Quick Settings and pass this test if a tile named
+ %1$s is visible in some page.
+ </string>
+ <string name="tiles_request_check_tile_already_added">Checking tile already added response
+ </string>
<string name="vr_tests">VR Tests</string>
<string name="test_category_vr">VR</string>
<string name="vr_test_title">VR Listener Test</string>
@@ -5009,16 +5059,13 @@
<string name="error_screen_pinning_couldnt_exit">Could not exit screen pinning through API.</string>
<!-- Audio Devices Notifications Tests -->
+ <string name="audio_devices_notification_instructions">
+ Connect and disconnect a wired headset.
+ Note if the appropriate notification messages appear below.
+ The \"Pass\" button will be enabled if device connect and disconnect messages are received.
+ </string>
<string name="audio_out_devices_notifications_test">Audio Output Devices Notifications Test</string>
- <string name="audio_out_devices_notification_instructions">
- Click the "Clear Messages" button then connect and disconnect a wired headset.
- Note if the appropriate notification messages appear below.
- </string>
<string name="audio_in_devices_notifications_test">Audio Input Devices Notifications Test</string>
- <string name="audio_in_devices_notification_instructions">
- Click the "Clear Messages" button then connect and disconnect a microphone or wired headset.
- Note if the appropriate notification messages appear below.
- </string>
<string name="audio_dev_notification_clearmsgs">Clear Messages</string>
<string name="audio_dev_notification_connectMsg">CONNECT DETECTED</string>
<string name="audio_dev_notification_disconnectMsg">DISCONNECT DETECTED</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java
index ab02210..0f352f5 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java
@@ -21,6 +21,7 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
@@ -46,6 +47,8 @@
// Whether test case was executed through automation.
protected boolean mIsAutomated;
+ protected final String mTag = getClass().getSimpleName();
+
protected void setTestListAdapter(TestListAdapter adapter) {
mAdapter = adapter;
setListAdapter(mAdapter);
@@ -128,6 +131,8 @@
/** Override this in subclasses instead of onListItemClick */
protected void handleItemClick(ListView listView, View view, int position, long id) {
Intent intent = getIntent(position);
+ Log.i(mTag, "Launching activity with " + IntentDrivenTestActivity.toString(this, intent));
+
startActivityForResult(intent, LAUNCH_TEST_REQUEST_CODE);
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/IntentDrivenTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/IntentDrivenTestActivity.java
index cc072a1..a1c1a0e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/IntentDrivenTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/IntentDrivenTestActivity.java
@@ -4,10 +4,12 @@
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
@@ -15,6 +17,11 @@
import android.widget.Button;
import android.widget.TextView;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
/**
* A generic activity for intent based tests.
*
@@ -28,32 +35,30 @@
* will dynamically create the intent when the button is clicked based on the test id and the
* button that was clicked.
*/
-public class IntentDrivenTestActivity extends PassFailButtons.Activity implements OnClickListener {
- private static final String TAG = "IntentDrivenTestActivity";
+public final class IntentDrivenTestActivity extends PassFailButtons.Activity
+ implements OnClickListener {
+
+ private static final String TAG = IntentDrivenTestActivity.class.getSimpleName();
public static final String EXTRA_ID = "id";
public static final String EXTRA_TITLE = "title";
public static final String EXTRA_INFO = "info";
public static final String EXTRA_BUTTONS = "buttons";
+ private static final String[] REQUIRED_EXTRAS = {
+ EXTRA_ID, EXTRA_TITLE, EXTRA_INFO, EXTRA_BUTTONS
+ };
+
private String mTestId;
private ButtonInfo[] mButtonInfos;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.intent_driven_test);
setPassFailButtonClickListeners();
- final Intent intent = getIntent();
- if (!intent.hasExtra(EXTRA_ID)
- || !intent.hasExtra(EXTRA_TITLE)
- || !intent.hasExtra(EXTRA_INFO)
- || !intent.hasExtra(EXTRA_BUTTONS)) {
- throw new IllegalArgumentException(
- "Intent must have EXTRA_ID, EXTRA_TITLE, EXTRA_INFO & EXTRA_BUTTONS");
- }
+ final Intent intent = checkAndGetIntent();
mTestId = intent.getStringExtra(EXTRA_ID);
setTitle(intent.getIntExtra(EXTRA_TITLE, -1));
@@ -130,7 +135,7 @@
return mTestId;
}
- public static class TestInfo {
+ public static final class TestInfo {
private final String mTestId;
private final int mTitle;
private final int mInfoText;
@@ -145,7 +150,7 @@
if (buttons.length > 2) {
throw new RuntimeException("Too many buttons");
}
- mTestId = testId;
+ mTestId = nonEmpty("testId", testId);
mTitle = title;
mInfoText = infoText;
mButtons = buttons;
@@ -168,7 +173,7 @@
}
}
- public static class ButtonInfo implements Parcelable {
+ public static final class ButtonInfo implements Parcelable {
private final int mButtonText;
private final Intent[] mIntents;
private final String mIntentFactoryClassName;
@@ -238,4 +243,57 @@
public interface IntentFactory {
Intent[] createIntents(String testId, int buttonText);
}
+
+ public static Intent newIntent(Context context, String testId, int titleResId,
+ int infoResId, ButtonInfo[] buttons) {
+ Intent intent = new Intent(context, IntentDrivenTestActivity.class)
+ .putExtra(IntentDrivenTestActivity.EXTRA_ID, nonEmpty("testId", testId))
+ .putExtra(IntentDrivenTestActivity.EXTRA_TITLE, titleResId)
+ .putExtra(IntentDrivenTestActivity.EXTRA_INFO, infoResId)
+ .putExtra(IntentDrivenTestActivity.EXTRA_BUTTONS,
+ Objects.requireNonNull(buttons, "buttons cannot be null"));
+ Log.d(TAG, "Added 4 required extras to " + toString(context, intent));
+ return intent;
+ }
+
+ private static String nonEmpty(String name, String value) {
+ if (TextUtils.isEmpty(value)) {
+ throw new IllegalArgumentException(name + " cannot be null or empty: '" + value + "'");
+ }
+ return value;
+ }
+
+ private Intent checkAndGetIntent() {
+ Intent intent = getIntent();
+ List<String> missingExtras = new ArrayList<>();
+ for (String extra : REQUIRED_EXTRAS) {
+ if (!intent.hasExtra(extra)) {
+ missingExtras.add(extra);
+ }
+ }
+ if (!missingExtras.isEmpty()) {
+ throw new IllegalArgumentException(toString(this, intent) + ") is missing "
+ + missingExtras.size() + " required extras: "
+ + Arrays.toString(REQUIRED_EXTRAS));
+ }
+ return intent;
+ }
+
+ public static String toString(Context context, Intent intent) {
+ return new StringBuilder("Intent["
+ + "address=").append(System.identityHashCode(intent))
+ .append(", numberExtras=").append(intent.getExtras().size())
+ .append(", extraKeys=").append(new ArrayList<>(intent.getExtras().keySet()))
+ .append(", testId=")
+ .append(intent.hasExtra(EXTRA_ID) ? intent.getStringExtra(EXTRA_ID) : "N/A")
+ .append(", title=").append(intent.hasExtra(EXTRA_TITLE)
+ ? context.getString(intent.getIntExtra(EXTRA_TITLE, -1))
+ : "N/A")
+ .append(intent.hasExtra(EXTRA_INFO) ? ", has_info" : ", no_info")
+ .append(", numberButtons=").append(intent.hasExtra(EXTRA_BUTTONS)
+ ? intent.getParcelableArrayExtra(EXTRA_BUTTONS).length
+ : 0)
+ .append(", rawIntent=").append(intent)
+ .append(']').toString();
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
index 4bd642d..0294ff7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
@@ -70,7 +70,10 @@
private static final String INFO_DIALOG_MESSAGE_ID = "infoDialogMessageId";
// ReportLog file for CTS-Verifier. The "stream" name gets mapped to the test class name.
- private static final String REPORT_LOG_NAME = "CTS-Verifier-Log";
+ public static final String GENERAL_TESTS_REPORT_LOG_NAME = "CtsVerifierGeneralTestCases";
+ public static final String AUDIO_TESTS_REPORT_LOG_NAME = "CtsVerifierAudioTestCases";
+
+ private static final String SECTION_UNDEFINED = "undefined_section_name";
// Interface mostly for making documentation and refactoring easier...
public interface PassFailActivity {
@@ -112,10 +115,16 @@
void setTestResultAndFinish(boolean passed);
/**
- * @return A unique name (derived from the test class name) to serve as a section
- * header in the CtsVerifierReportLog file.
+ * @return The name of the file to store the (suite of) ReportLog information.
*/
- String getReportSectionName();
+ public String getReportFileName();
+
+ /**
+ * @return A unique name to serve as a section header in the CtsVerifierReportLog file.
+ * Tests need to conform to the underscore_delineated_name standard for use with
+ * the protobuff/json ReportLog parsing in Google3
+ */
+ public String getReportSectionName();
/**
* Test subclasses can override this to record their CtsVerifierReportLogs.
@@ -138,7 +147,7 @@
private final TestResultHistoryCollection mHistoryCollection;
public Activity() {
- this.mReportLog = new CtsVerifierReportLog(REPORT_LOG_NAME, getReportSectionName());
+ this.mReportLog = new CtsVerifierReportLog(getReportFileName(), getReportSectionName());
this.mHistoryCollection = new TestResultHistoryCollection();
}
@@ -202,9 +211,15 @@
return mReportLog;
}
+ /**
+ * @return The name of the file to store the (suite of) ReportLog information.
+ */
@Override
- public final String getReportSectionName() {
- return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
+ public String getReportFileName() { return GENERAL_TESTS_REPORT_LOG_NAME; }
+
+ @Override
+ public String getReportSectionName() {
+ return setTestNameSuffix(sCurrentDisplayMode, SECTION_UNDEFINED);
}
@Override
@@ -240,7 +255,7 @@
private final TestResultHistoryCollection mHistoryCollection;
public ListActivity() {
- this.mReportLog = new CtsVerifierReportLog(REPORT_LOG_NAME, getReportSectionName());
+ this.mReportLog = new CtsVerifierReportLog(getReportFileName(), getReportSectionName());
this.mHistoryCollection = new TestResultHistoryCollection();
}
@@ -286,9 +301,15 @@
return mReportLog;
}
+ /**
+ * @return The name of the file to store the (suite of) ReportLog information.
+ */
@Override
- public final String getReportSectionName() {
- return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
+ public String getReportFileName() { return GENERAL_TESTS_REPORT_LOG_NAME; }
+
+ @Override
+ public String getReportSectionName() {
+ return setTestNameSuffix(sCurrentDisplayMode, SECTION_UNDEFINED);
}
@Override
@@ -326,9 +347,9 @@
public TestListActivity() {
// TODO(b/186555602): temporary hack^H^H^H^H workaround to fix crash
// This DOES NOT in fact fix that bug.
- // if (true) this.mReportLog = new CtsVerifierReportLog(REPORT_LOG_NAME, "42"); else
+ // if (true) this.mReportLog = new CtsVerifierReportLog(b/186555602, "42"); else
- this.mReportLog = new CtsVerifierReportLog(REPORT_LOG_NAME, getReportSectionName());
+ this.mReportLog = new CtsVerifierReportLog(getReportFileName(), getReportSectionName());
}
@Override
@@ -373,11 +394,18 @@
return mReportLog;
}
+ /**
+ * @return The name of the file to store the (suite of) ReportLog information.
+ */
@Override
- public final String getReportSectionName() {
- return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
+ public String getReportFileName() { return GENERAL_TESTS_REPORT_LOG_NAME; }
+
+ @Override
+ public String getReportSectionName() {
+ return setTestNameSuffix(sCurrentDisplayMode, SECTION_UNDEFINED);
}
+
/**
* Get existing test history to aggregate.
*/
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
index 6233f2c..9ff7028 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
@@ -52,6 +52,9 @@
import org.hyphonate.megaaudio.player.PlayerBuilder;
import org.hyphonate.megaaudio.player.sources.SinAudioSourceProvider;
+import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
+import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
+
public class AnalogHeadsetAudioActivity
extends PassFailButtons.Activity
implements View.OnClickListener {
@@ -105,6 +108,17 @@
JavaPlayer mAudioPlayer;
+ // ReportLog Schema
+ private static final String SECTION_ANALOG_HEADSET = "analog_headset_activity";
+ private static final String KEY_HAS_HEADSET_PORT = "has_headset_port";
+ private static final String KEY_HEADSET_PLUG_INTENT_STATE = "intent_received_state";
+ private static final String KEY_CLAIMS_HEADSET_PORT = "claims_headset_port";
+ private static final String KEY_HEADSET_CONNECTED = "headset_connected";
+ private static final String KEY_KEYCODE_HEADSETHOOK = "keycode_headset_hook";
+ private static final String KEY_KEYCODE_PLAY_PAUSE = "keycode_play_pause";
+ private static final String KEY_KEYCODE_VOLUME_UP = "keycode_volume_up";
+ private static final String KEY_KEYCODE_VOLUME_DOWN = "keycode_volume_down";
+
public AnalogHeadsetAudioActivity() {
super();
}
@@ -186,10 +200,26 @@
}
}
+ //
+ // PassFailButtons Overrides
+ //
+ @Override
+ public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; }
+
+ @Override
+ public final String getReportSectionName() {
+ return setTestNameSuffix(sCurrentDisplayMode, SECTION_ANALOG_HEADSET);
+ }
+
+ @Override
+ public void recordTestResults() {
+ getReportLog().submit();
+ }
+
private void reportHeadsetPort(boolean has) {
mHasHeadsetPort = has;
getReportLog().addValue(
- "User Reports Headset Port",
+ KEY_HAS_HEADSET_PORT,
has ? 1 : 0,
ResultType.NEUTRAL,
ResultUnit.NONE);
@@ -247,7 +277,7 @@
}
getReportLog().addValue(
- "ACTION_HEADSET_PLUG Intent Received. State: ",
+ KEY_HEADSET_PLUG_INTENT_STATE,
state,
ResultType.NEUTRAL,
ResultUnit.NONE);
@@ -264,7 +294,7 @@
getPassButton().setEnabled(calculatePass());
getReportLog().addValue(
- "User reported headset/headphones playback",
+ KEY_CLAIMS_HEADSET_PORT,
success ? 1 : 0,
ResultType.NEUTRAL,
ResultUnit.NONE);
@@ -389,18 +419,15 @@
if (devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADPHONES) {
mHeadsetDeviceInfo = devInfo;
-
- getReportLog().addValue(
- (devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET
- ? "Headset" : "Headphones") + " connected",
- 0,
- ResultType.NEUTRAL,
- ResultUnit.NONE);
break;
}
}
- reportHeadsetPort(mHeadsetDeviceInfo != null);
+ getReportLog().addValue(
+ KEY_HEADSET_CONNECTED,
+ mHeadsetDeviceInfo != null ? 1 : 0,
+ ResultType.NEUTRAL,
+ ResultUnit.NONE);
showConnectedDevice();
}
@@ -445,7 +472,7 @@
showKeyMessagesState();
getPassButton().setEnabled(calculatePass());
getReportLog().addValue(
- "KEYCODE_HEADSETHOOK", 1, ResultType.NEUTRAL, ResultUnit.NONE);
+ KEY_KEYCODE_HEADSETHOOK, 1, ResultType.NEUTRAL, ResultUnit.NONE);
break;
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
@@ -453,7 +480,7 @@
showKeyMessagesState();
getPassButton().setEnabled(calculatePass());
getReportLog().addValue(
- "KEYCODE_MEDIA_PLAY_PAUSE", 1, ResultType.NEUTRAL, ResultUnit.NONE);
+ KEY_KEYCODE_PLAY_PAUSE, 1, ResultType.NEUTRAL, ResultUnit.NONE);
break;
case KeyEvent.KEYCODE_VOLUME_UP:
@@ -461,7 +488,7 @@
showKeyMessagesState();
getPassButton().setEnabled(calculatePass());
getReportLog().addValue(
- "KEYCODE_VOLUME_UP", 1, ResultType.NEUTRAL, ResultUnit.NONE);
+ KEY_KEYCODE_VOLUME_UP, 1, ResultType.NEUTRAL, ResultUnit.NONE);
break;
case KeyEvent.KEYCODE_VOLUME_DOWN:
@@ -469,7 +496,7 @@
showKeyMessagesState();
getPassButton().setEnabled(calculatePass());
getReportLog().addValue(
- "KEYCODE_VOLUME_DOWN", 1, ResultType.NEUTRAL, ResultUnit.NONE);
+ KEY_KEYCODE_VOLUME_DOWN, 1, ResultType.NEUTRAL, ResultUnit.NONE);
break;
}
return super.onKeyDown(keyCode, event);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java
index 64c2314..6a7d92e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java
@@ -16,22 +16,16 @@
package com.android.cts.verifier.audio;
-import com.android.cts.verifier.R;
-
import android.content.Context;
-
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
-
import android.os.Bundle;
-
import android.view.View;
-import android.view.View.OnClickListener;
-
-import android.widget.Button;
import android.widget.TextView;
+import com.android.cts.verifier.R;
+
/**
* Tests Audio Device Connection events for output by prompting the user to insert/remove a
* wired headset (or microphone) and noting the presence (or absence) of notifications.
@@ -41,13 +35,24 @@
TextView mConnectView;
TextView mDisconnectView;
- Button mClearMsgsBtn;
+ TextView mInfoView;
+
+ boolean mHandledInitialAddedMessage = false;
+ boolean mConnectReceived = false;
+ boolean mDisconnectReceived = false;
private class TestAudioDeviceCallback extends AudioDeviceCallback {
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+ // we will get this message when we setup the handler, so ignore the first one.
+ if (!mHandledInitialAddedMessage) {
+ mHandledInitialAddedMessage = true;
+ return;
+ }
if (addedDevices.length != 0) {
mConnectView.setText(
mContext.getResources().getString(R.string.audio_dev_notification_connectMsg));
+ mConnectReceived = true;
+ getPassButton().setEnabled(mConnectReceived && mDisconnectReceived);
}
}
@@ -56,35 +61,25 @@
mDisconnectView.setText(
mContext.getResources().getString(
R.string.audio_dev_notification_disconnectMsg));
+ mDisconnectReceived = true;
+ getPassButton().setEnabled(mConnectReceived && mDisconnectReceived);
}
}
}
@Override
- protected void enableTestButtons(boolean enabled) {
- // Nothing to do.
- }
-
- @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.audio_dev_notify);
mContext = this;
- mConnectView = (TextView)findViewById(R.id.audio_dev_notification_connect_msg);
- mDisconnectView = (TextView)findViewById(R.id.audio_dev_notification_disconnect_msg);
+ mConnectView = (TextView) findViewById(R.id.audio_dev_notification_connect_msg);
+ mDisconnectView = (TextView) findViewById(R.id.audio_dev_notification_disconnect_msg);
- ((TextView)findViewById(R.id.info_text)).setText(mContext.getResources().getString(
- R.string.audio_in_devices_notification_instructions));
-
- mClearMsgsBtn = (Button)findViewById(R.id.audio_dev_notification_connect_clearmsgs_btn);
- mClearMsgsBtn.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- mConnectView.setText("");
- mDisconnectView.setText("");
- }
- });
+ mInfoView = (TextView) findViewById(R.id.info_text);
+ mInfoView.setText(mContext.getResources().getString(
+ R.string.audio_devices_notification_instructions));
AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
audioManager.registerAudioDeviceCallback(new TestAudioDeviceCallback(), null);
@@ -94,4 +89,9 @@
setPassFailButtonClickListeners();
}
+
+ @Override
+ protected void enableTestButtons(boolean enabled) {
+ mInfoView.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
index 4b2d213..4895cde 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
@@ -16,30 +16,22 @@
package com.android.cts.verifier.audio;
-import com.android.cts.verifier.R;
-
import android.content.Context;
-
-import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
-import android.media.AudioFormat;
-import android.media.AudioManager;
import android.media.AudioRecord;
-
import android.os.Bundle;
import android.os.Handler;
-
import android.util.Log;
-
import android.view.View;
import android.view.View.OnClickListener;
-
import android.widget.Button;
import android.widget.TextView;
-import org.hyphonate.megaaudio.recorder.RecorderBuilder;
-import org.hyphonate.megaaudio.recorder.Recorder;
+import com.android.cts.verifier.R;
+
import org.hyphonate.megaaudio.recorder.JavaRecorder;
+import org.hyphonate.megaaudio.recorder.Recorder;
+import org.hyphonate.megaaudio.recorder.RecorderBuilder;
import org.hyphonate.megaaudio.recorder.sinks.NopAudioSinkProvider;
/*
@@ -50,10 +42,11 @@
Button recordBtn;
Button stopBtn;
+ TextView mInfoView;
Context mContext;
- int mNumRecordNotifications = 0;
+ int mNumRoutingNotifications;
OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
@@ -61,7 +54,12 @@
static final int SAMPLE_RATE = 48000;
int mNumFrames;
- JavaRecorder mAudioRecorder;
+ private JavaRecorder mAudioRecorder;
+ private AudioRecordRoutingChangeListener mRouteChangeListener;
+ private boolean mIsRecording;
+
+ // ignore messages sent as a consequence of starting the player
+ private static final int NUM_IGNORE_MESSAGES = 2;
private class OnBtnClickListener implements OnClickListener {
@Override
@@ -72,26 +70,50 @@
switch (v.getId()) {
case R.id.audio_routingnotification_recordBtn:
- {
- mAudioRecorder.startStream();
-
- AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
- audioRecord.addOnRoutingChangedListener(
- new AudioRecordRoutingChangeListener(), new Handler());
-
- }
+ startRecording();
break;
case R.id.audio_routingnotification_recordStopBtn:
- mAudioRecorder.stopStream();
break;
}
}
}
+ private void startRecording() {
+ if (!mIsRecording) {
+ mNumRoutingNotifications = 0;
+
+ mAudioRecorder.startStream();
+
+ AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
+ audioRecord.addOnRoutingChangedListener(mRouteChangeListener,
+ new Handler());
+
+ mIsRecording = true;
+ enableRecordButtons(!mIsRecording, mIsRecording);
+ }
+ }
+
+ private void stopRecording() {
+ if (mIsRecording) {
+ mAudioRecorder.stopStream();
+
+ AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
+ audioRecord.removeOnRoutingChangedListener(mRouteChangeListener);
+
+ mIsRecording = false;
+ enableRecordButtons(!mIsRecording, mIsRecording);
+ }
+ }
+
private class AudioRecordRoutingChangeListener implements AudioRecord.OnRoutingChangedListener {
public void onRoutingChanged(AudioRecord audioRecord) {
- mNumRecordNotifications++;
+ // Starting recording triggers routing messages, so ignore the first one.
+ mNumRoutingNotifications++;
+ if (mNumRoutingNotifications <= NUM_IGNORE_MESSAGES) {
+ return;
+ }
+
TextView textView =
(TextView)findViewById(R.id.audio_routingnotification_audioRecord_change);
String msg = mContext.getResources().getString(
@@ -101,13 +123,20 @@
int deviceType = routedDevice != null ? routedDevice.getType() : -1;
textView.setText(msg + " - " +
deviceName + " [0x" + Integer.toHexString(deviceType) + "]" +
- " - " + mNumRecordNotifications);
+ " - " + mNumRoutingNotifications);
+ getPassButton().setEnabled(true);
}
}
+ @Override
protected void enableTestButtons(boolean enabled) {
- recordBtn.setEnabled(enabled);
- stopBtn.setEnabled(enabled);
+ enableRecordButtons(!mIsRecording, mIsRecording);
+ mInfoView.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ void enableRecordButtons(boolean enableRecord, boolean enableStop) {
+ recordBtn.setEnabled(enableRecord);
+ stopBtn.setEnabled(enableStop);
}
@Override
@@ -116,11 +145,15 @@
setContentView(R.layout.audio_input_routingnotifications_test);
Button btn;
- recordBtn = (Button)findViewById(R.id.audio_routingnotification_recordBtn);
+ recordBtn = (Button) findViewById(R.id.audio_routingnotification_recordBtn);
recordBtn.setOnClickListener(mBtnClickListener);
- stopBtn = (Button)findViewById(R.id.audio_routingnotification_recordStopBtn);
+ stopBtn = (Button) findViewById(R.id.audio_routingnotification_recordStopBtn);
stopBtn.setOnClickListener(mBtnClickListener);
+ enableRecordButtons(false, false);
+
+ mInfoView = (TextView) findViewById(R.id.info_text);
+
mContext = this;
// Setup Recorder
@@ -137,17 +170,18 @@
Log.e(TAG, "Failed MegaRecorder build.");
}
+ mRouteChangeListener = new AudioRecordRoutingChangeListener();
+ AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
+ audioRecord.addOnRoutingChangedListener(mRouteChangeListener, new Handler());
+
// "Honor System" buttons
super.setup();
-
setPassFailButtonClickListeners();
}
@Override
public void onBackPressed () {
- if (mAudioRecorder != null) {
- mAudioRecorder.stopStream();
- }
+ stopRecording();
super.onBackPressed();
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
deleted file mode 100644
index 78e34d3..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
+++ /dev/null
@@ -1,686 +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.verifier.audio;
-
-import android.app.AlertDialog;
-import android.media.AudioDeviceCallback;
-import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
-import android.media.MediaRecorder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.ProgressBar;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.audio.audiolib.AudioSystemFlags;
-import com.android.cts.verifier.audio.audiolib.StatUtils;
-import com.android.cts.verifier.audio.audiolib.AudioUtils;
-import com.android.cts.verifier.CtsVerifierReportLog;
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
-import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
-
-/**
- * Base class for testing activitiees that require audio loopback hardware..
- */
-public class AudioLoopbackBaseActivity extends PassFailButtons.Activity {
- private static final String TAG = "AudioLoopbackBaseActivity";
-
- // JNI load
- static {
- try {
- System.loadLibrary("audioloopback_jni");
- } catch (UnsatisfiedLinkError e) {
- Log.e(TAG, "Error loading Audio Loopback JNI library");
- Log.e(TAG, "e: " + e);
- e.printStackTrace();
- }
-
- /* TODO: gracefully fail/notify if the library can't be loaded */
- }
- protected AudioManager mAudioManager;
-
- // UI
- TextView mInputDeviceTxt;
- TextView mOutputDeviceTxt;
-
- TextView mAudioLevelText;
- SeekBar mAudioLevelSeekbar;
-
- TextView mTestPathTxt;
-
- TextView mResultText;
- ProgressBar mProgressBar;
- int mMaxLevel;
-
- OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
- protected Button mTestButton;
-
- String mYesString;
- String mNoString;
-
- // These flags determine the maximum allowed latency
- private boolean mClaimsProAudio;
- private boolean mClaimsOutput;
- private boolean mClaimsInput;
-
- // Useful info
- private boolean mSupportsMMAP = AudioUtils.isMMapSupported();
- private boolean mSupportsMMAPExclusive = AudioUtils.isMMapExclusiveSupported();
-
- // Peripheral(s)
- boolean mIsPeripheralAttached; // CDD ProAudio section C-1-3
- AudioDeviceInfo mOutputDevInfo;
- AudioDeviceInfo mInputDevInfo;
-
- protected static final int TESTPERIPHERAL_INVALID = -1;
- protected static final int TESTPERIPHERAL_NONE = 0;
- protected static final int TESTPERIPHERAL_ANALOG_JACK = 1;
- protected static final int TESTPERIPHERAL_USB = 2;
- protected static final int TESTPERIPHERAL_DEVICE = 3; // device speaker + mic
- protected int mTestPeripheral = TESTPERIPHERAL_NONE;
-
- // Loopback Logic
- NativeAnalyzerThread mNativeAnalyzerThread = null;
-
- protected static final int NUM_TEST_PHASES = 5;
- protected int mTestPhase = 0;
-
- protected double[] mLatencyMillis = new double[NUM_TEST_PHASES];
- protected double[] mConfidence = new double[NUM_TEST_PHASES];
-
- protected double mMeanLatencyMillis;
- protected double mMeanAbsoluteDeviation;
- protected double mMeanConfidence;
-
- protected static final double CONFIDENCE_THRESHOLD = 0.6;
- // impossibly low latencies (indicating something in the test went wrong).
- protected static final float EPSILON = 1.0f;
- protected static final double PROAUDIO_RECOMMENDED_LATENCY_MS = 20.0;
- protected static final double PROAUDIO_RECOMMENDED_USB_LATENCY_MS = 25.0;
- protected static final double PROAUDIO_MUST_LATENCY_MS = 20.0;
- protected static final double BASIC_RECOMMENDED_LATENCY_MS = 50.0;
- protected static final double BASIC_MUST_LATENCY_MS = 800.0;
- protected double mMustLatency;
- protected double mRecommendedLatency;
-
- // The audio stream callback threads should stop and close
- // in less than a few hundred msec. This is a generous timeout value.
- private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000;
-
- //
- // Common UI Handling
- //
- private void connectLoopbackUI() {
- // Connected Device
- mInputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackInputLbl));
- mOutputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackOutputLbl));
-
- mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text);
- mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar);
- mMaxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
- mAudioLevelSeekbar.setMax(mMaxLevel);
- mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0);
- refreshLevel();
-
- mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {}
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {}
-
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
- progress, 0);
- Log.i(TAG,"Level set to: " + progress);
- refreshLevel();
- }
- });
-
- mResultText = (TextView)findViewById(R.id.audio_loopback_results_text);
- mProgressBar = (ProgressBar)findViewById(R.id.audio_loopback_progress_bar);
- showWait(false);
- }
-
- //
- // Peripheral Connection Logic
- //
- protected void scanPeripheralList(AudioDeviceInfo[] devices) {
- // CDD Section C-1-3: USB port, host-mode support
-
- // Can't just use the first record because then we will only get
- // Source OR sink, not both even on devices that are both.
- mOutputDevInfo = null;
- mInputDevInfo = null;
-
- // Any valid peripherals
- // Do we leave in the Headset test to support a USB-Dongle?
- for (AudioDeviceInfo devInfo : devices) {
- if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE || // USB Peripheral
- devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET || // USB dongle+LBPlug
- devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET || // Loopback Plug?
- devInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) { // Aux-cable loopback?
- if (devInfo.isSink()) {
- mOutputDevInfo = devInfo;
- }
- if (devInfo.isSource()) {
- mInputDevInfo = devInfo;
- }
- } else {
- handleDeviceConnection(devInfo);
- }
- }
-
- // need BOTH input and output to test
- mIsPeripheralAttached = mOutputDevInfo != null && mInputDevInfo != null;
- calculateTestPeripheral();
- showConnectedAudioPeripheral();
- calculateLatencyThresholds();
- displayLatencyThresholds();
- }
-
- protected void handleDeviceConnection(AudioDeviceInfo deviceInfo) {
- // NOP
- }
-
- private class ConnectListener extends AudioDeviceCallback {
- /*package*/ ConnectListener() {}
-
- //
- // AudioDevicesManager.OnDeviceConnectionListener
- //
- @Override
- public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
- scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
- }
-
- @Override
- public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
- scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
- }
- }
-
- private void calculateTestPeripheral() {
- if (!mIsPeripheralAttached) {
- if ((mOutputDevInfo != null && mInputDevInfo == null)
- || (mOutputDevInfo == null && mInputDevInfo != null)) {
- mTestPeripheral = TESTPERIPHERAL_INVALID;
- } else {
- mTestPeripheral = TESTPERIPHERAL_DEVICE;
- }
- } else if (!areIODevicesOnePeripheral()) {
- mTestPeripheral = TESTPERIPHERAL_INVALID;
- } else if (mInputDevInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE ||
- mInputDevInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET) {
- mTestPeripheral = TESTPERIPHERAL_USB;
- } else if (mInputDevInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
- mInputDevInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) {
- mTestPeripheral = TESTPERIPHERAL_ANALOG_JACK;
- } else {
- // Huh?
- Log.e(TAG, "No valid peripheral found!?");
- mTestPeripheral = TESTPERIPHERAL_NONE;
- }
- }
-
- private boolean isPeripheralValidForTest() {
- return mTestPeripheral == TESTPERIPHERAL_ANALOG_JACK
- || mTestPeripheral == TESTPERIPHERAL_USB;
- }
-
- private void showConnectedAudioPeripheral() {
- mInputDeviceTxt.setText(
- mInputDevInfo != null ? mInputDevInfo.getProductName().toString()
- : "Not connected");
- mOutputDeviceTxt.setText(
- mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString()
- : "Not connected");
-
- String pathName;
- switch (mTestPeripheral) {
- case TESTPERIPHERAL_INVALID:
- pathName = "Invalid Test Peripheral";
- break;
-
- case TESTPERIPHERAL_ANALOG_JACK:
- pathName = "Headset Jack";
- break;
-
- case TESTPERIPHERAL_USB:
- pathName = "USB";
- break;
-
- case TESTPERIPHERAL_DEVICE:
- pathName = "Device Speaker + Microphone";
- break;
-
- case TESTPERIPHERAL_NONE:
- default:
- pathName = "Error. Unknown Test Path";
- break;
- }
- mTestPathTxt.setText(pathName);
- mTestButton.setEnabled(
- mTestPeripheral != TESTPERIPHERAL_INVALID && mTestPeripheral != TESTPERIPHERAL_NONE);
-
- }
-
- private boolean areIODevicesOnePeripheral() {
- if (mOutputDevInfo == null || mInputDevInfo == null) {
- return false;
- }
-
- return mOutputDevInfo.getProductName().toString().equals(
- mInputDevInfo.getProductName().toString());
- }
-
- private void calculateLatencyThresholds() {
- switch (mTestPeripheral) {
- case TESTPERIPHERAL_ANALOG_JACK:
- mRecommendedLatency = mClaimsProAudio
- ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
- mMustLatency = mClaimsProAudio
- ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_MUST_LATENCY_MS;
- break;
-
- case TESTPERIPHERAL_USB:
- mRecommendedLatency = mClaimsProAudio
- ? PROAUDIO_RECOMMENDED_USB_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
- mMustLatency = mClaimsProAudio
- ? PROAUDIO_RECOMMENDED_USB_LATENCY_MS : BASIC_MUST_LATENCY_MS;
- break;
-
- case TESTPERIPHERAL_DEVICE:
- // This isn't a valid case so we won't pass it, but it can be run
- mRecommendedLatency = mClaimsProAudio
- ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
- mMustLatency = mClaimsProAudio
- ? PROAUDIO_RECOMMENDED_LATENCY_MS :BASIC_MUST_LATENCY_MS;
- break;
-
- case TESTPERIPHERAL_NONE:
- default:
- mRecommendedLatency = BASIC_RECOMMENDED_LATENCY_MS;
- mMustLatency = BASIC_MUST_LATENCY_MS;
- break;
- }
- }
-
- private void displayLatencyThresholds() {
- if (isPeripheralValidForTest()) {
- ((TextView) findViewById(R.id.audio_loopback_must_latency)).setText("" + mMustLatency);
- ((TextView) findViewById(R.id.audio_loopback_recommended_latency)).setText(
- "" + mRecommendedLatency);
- } else {
- String naStr = getResources().getString(R.string.audio_proaudio_NA);
- ((TextView) findViewById(R.id.audio_loopback_must_latency)).setText(naStr);
- ((TextView) findViewById(R.id.audio_loopback_recommended_latency)).setText(naStr);
- }
- }
-
- /**
- * refresh Audio Level seekbar and text
- */
- private void refreshLevel() {
- int currentLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
- mAudioLevelSeekbar.setProgress(currentLevel);
-
- String levelText = String.format("%s: %d/%d",
- getResources().getString(R.string.audio_loopback_level_text),
- currentLevel, mMaxLevel);
- mAudioLevelText.setText(levelText);
- }
-
- //
- // show active progress bar
- //
- protected void showWait(boolean show) {
- mProgressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
- }
-
- //
- // Common loging
- //
- // Schema
- private static final String KEY_LATENCY = "latency";
- private static final String KEY_CONFIDENCE = "confidence";
- private static final String KEY_SAMPLE_RATE = "sample_rate";
- private static final String KEY_IS_PRO_AUDIO = "is_pro_audio";
- private static final String KEY_IS_LOW_LATENCY = "is_low_latency";
- private static final String KEY_IS_PERIPHERAL_ATTACHED = "is_peripheral_attached";
- private static final String KEY_INPUT_PERIPHERAL_NAME = "input_peripheral";
- private static final String KEY_OUTPUT_PERIPHERAL_NAME = "output_peripheral";
- private static final String KEY_TEST_PERIPHERAL = "test_peripheral";
- private static final String KEY_TEST_MMAP = "supports_mmap";
- private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive";
- private static final String KEY_LEVEL = "level";
- private static final String KEY_BUFFER_SIZE = "buffer_size_in_frames";
-
- @Override
- public String getTestId() {
- return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
- }
-
- //
- // Subclasses should call this explicitly. SubClasses should call submit() after their logs
- //
- @Override
- public void recordTestResults() {
- if (mNativeAnalyzerThread == null) {
- return; // no results to report
- }
-
- CtsVerifierReportLog reportLog = getReportLog();
- reportLog.addValue(
- KEY_LATENCY,
- mMeanLatencyMillis,
- ResultType.LOWER_BETTER,
- ResultUnit.MS);
-
- reportLog.addValue(
- KEY_CONFIDENCE,
- mMeanConfidence,
- ResultType.HIGHER_BETTER,
- ResultUnit.NONE);
-
- reportLog.addValue(
- KEY_SAMPLE_RATE,
- mNativeAnalyzerThread.getSampleRate(),
- ResultType.NEUTRAL,
- ResultUnit.NONE);
-
- reportLog.addValue(
- KEY_IS_LOW_LATENCY,
- mNativeAnalyzerThread.isLowLatencyStream(),
- ResultType.NEUTRAL,
- ResultUnit.NONE);
-
- reportLog.addValue(
- KEY_IS_PERIPHERAL_ATTACHED,
- mIsPeripheralAttached,
- ResultType.NEUTRAL,
- ResultUnit.NONE);
-
- reportLog.addValue(
- KEY_IS_PRO_AUDIO,
- mClaimsProAudio,
- ResultType.NEUTRAL,
- ResultUnit.NONE);
-
- reportLog.addValue(
- KEY_TEST_PERIPHERAL,
- mTestPeripheral,
- ResultType.NEUTRAL,
- ResultUnit.NONE);
-
- reportLog.addValue(
- KEY_TEST_MMAP,
- mSupportsMMAP,
- ResultType.NEUTRAL,
- ResultUnit.NONE);
-
- reportLog.addValue(
- KEY_TEST_MMAPEXCLUSIVE ,
- mSupportsMMAPExclusive,
- ResultType.NEUTRAL,
- ResultUnit.NONE);
-
- if (mIsPeripheralAttached) {
- reportLog.addValue(
- KEY_INPUT_PERIPHERAL_NAME,
- mInputDevInfo != null ? mInputDevInfo.getProductName().toString() : "None",
- ResultType.NEUTRAL,
- ResultUnit.NONE);
-
- reportLog.addValue(
- KEY_OUTPUT_PERIPHERAL_NAME,
- mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString() : "None",
- ResultType.NEUTRAL,
- ResultUnit.NONE);
- }
-
- int audioLevel = mAudioLevelSeekbar.getProgress();
- reportLog.addValue(
- KEY_LEVEL,
- audioLevel,
- ResultType.NEUTRAL,
- ResultUnit.NONE);
-
- reportLog.submit();
- }
-
- private void startAudioTest(Handler messageHandler) {
- getPassButton().setEnabled(false);
-
- mTestPhase = 0;
- java.util.Arrays.fill(mLatencyMillis, 0.0);
- java.util.Arrays.fill(mConfidence, 0.0);
-
- mNativeAnalyzerThread = new NativeAnalyzerThread(this);
- if (mNativeAnalyzerThread != null) {
- mNativeAnalyzerThread.setMessageHandler(messageHandler);
- // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
- mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION);
- startTestPhase();
- } else {
- Log.e(TAG, "Couldn't allocate native analyzer thread");
- mResultText.setText(getResources().getString(R.string.audio_loopback_failure));
- }
- }
-
- private void startTestPhase() {
- if (mNativeAnalyzerThread != null) {
- mNativeAnalyzerThread.startTest();
-
- // what is this for?
- try {
- Thread.sleep(200);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
-
- private void handleTestCompletion() {
- mMeanLatencyMillis = StatUtils.calculateMean(mLatencyMillis);
- mMeanAbsoluteDeviation =
- StatUtils.calculateMeanAbsoluteDeviation(mMeanLatencyMillis, mLatencyMillis);
- mMeanConfidence = StatUtils.calculateMean(mConfidence);
-
- boolean pass = isPeripheralValidForTest()
- && mMeanConfidence >= CONFIDENCE_THRESHOLD
- && mMeanLatencyMillis > EPSILON
- && mMeanLatencyMillis < mMustLatency;
-
- getPassButton().setEnabled(pass);
-
- String result;
- if (mMeanConfidence < CONFIDENCE_THRESHOLD) {
- result = String.format(
- "Test Finished\nInsufficient Confidence (%.2f < %.2f). No Results.",
- mMeanConfidence, CONFIDENCE_THRESHOLD);
- } else {
- result = String.format(
- "Test Finished - %s\nMean Latency:%.2f ms (required:%.2f)\n" +
- "Mean Absolute Deviation: %.2f\n" +
- " Confidence: %.2f\n" +
- " Low Latency Path: %s",
- (pass ? "PASS" : "FAIL"),
- mMeanLatencyMillis,
- mMustLatency,
- mMeanAbsoluteDeviation,
- mMeanConfidence,
- mNativeAnalyzerThread.isLowLatencyStream() ? mYesString : mNoString);
- }
-
- // Make sure the test thread is finished. It should already be done.
- if (mNativeAnalyzerThread != null) {
- try {
- mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- mResultText.setText(result);
-
- recordTestResults();
-
- showWait(false);
- mTestButton.setEnabled(true);
- }
-
- private void handleTestPhaseCompletion() {
- if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) {
- mLatencyMillis[mTestPhase] = mNativeAnalyzerThread.getLatencyMillis();
- mConfidence[mTestPhase] = mNativeAnalyzerThread.getConfidence();
-
- String result = String.format(
- "Test %d Finished\nLatency: %.2f ms\nConfidence: %.2f\n",
- mTestPhase,
- mLatencyMillis[mTestPhase],
- mConfidence[mTestPhase]);
-
- mResultText.setText(result);
- try {
- mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
- // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- mTestPhase++;
- if (mTestPhase >= NUM_TEST_PHASES) {
- handleTestCompletion();
- } else {
- startTestPhase();
- }
- }
- }
-
- /**
- * handler for messages from audio thread
- */
- private Handler mMessageHandler = new Handler() {
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- switch(msg.what) {
- case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
- Log.v(TAG,"got message native rec started!!");
- showWait(true);
- mResultText.setText(String.format("[phase: %d] - Test Running...",
- (mTestPhase + 1)));
- break;
- case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR:
- Log.v(TAG,"got message native rec can't start!!");
- mResultText.setText("Test Error opening streams.");
- handleTestCompletion();
- break;
- case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR:
- Log.v(TAG,"got message native rec can't start!!");
- mResultText.setText("Test Error while recording.");
- handleTestCompletion();
- break;
- case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
- mResultText.setText("Test FAILED due to errors.");
- handleTestCompletion();
- break;
- case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING:
- Log.i(TAG, "NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING");
- mResultText.setText(String.format("[phase: %d] - Analyzing ...",
- mTestPhase + 1));
- break;
- case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
- Log.i(TAG, "NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE");
- handleTestPhaseCompletion();
- break;
- default:
- break;
- }
- }
- };
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.audio_loopback_latency_activity);
-
- setPassFailButtonClickListeners();
- getPassButton().setEnabled(false);
- setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1);
-
- mClaimsOutput = AudioSystemFlags.claimsOutput(this);
- mClaimsInput = AudioSystemFlags.claimsInput(this);
- mClaimsProAudio = AudioSystemFlags.claimsProAudio(this);
-
- mYesString = getResources().getString(R.string.audio_general_yes);
- mNoString = getResources().getString(R.string.audio_general_no);
-
- // Pro Audio
- ((TextView)findViewById(R.id.audio_loopback_pro_audio)).setText(
- "" + (mClaimsProAudio ? mYesString : mNoString));
-
- // MMAP
- ((TextView)findViewById(R.id.audio_loopback_mmap)).setText(
- "" + (mSupportsMMAP ? mYesString : mNoString));
- ((TextView)findViewById(R.id.audio_loopback_mmap_exclusive)).setText(
- "" + (mSupportsMMAPExclusive ? mYesString : mNoString));
-
- // Low Latency
- ((TextView)findViewById(R.id.audio_loopback_low_latency)).setText(
- "" + (AudioSystemFlags.claimsLowLatencyAudio(this) ? mYesString : mNoString));
-
- mTestPathTxt = ((TextView)findViewById(R.id.audio_loopback_testpath));
-
- mTestButton = (Button)findViewById(R.id.audio_loopback_test_btn);
- mTestButton.setOnClickListener(mBtnClickListener);
-
- mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
-
- mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
-
- connectLoopbackUI();
-
- calculateLatencyThresholds();
- displayLatencyThresholds();
- }
-
- private class OnBtnClickListener implements OnClickListener {
- @Override
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.audio_loopback_test_btn:
- Log.i(TAG, "audio loopback test");
- startAudioTest(mMessageHandler);
- break;
- }
- }
- }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java
index 9490091..8ee3446 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -16,9 +16,679 @@
package com.android.cts.verifier.audio;
+import android.app.AlertDialog;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.MediaRecorder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.cts.verifier.audio.audiolib.AudioSystemFlags;
+import com.android.cts.verifier.audio.audiolib.StatUtils;
+import com.android.cts.verifier.audio.audiolib.AudioUtils;
+import com.android.cts.verifier.CtsVerifierReportLog;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
+import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
+
/**
- * Tests Audio Device roundtrip latency by using a loopback plug.
+ * CtsVerifier Audio Loopback Latency Test
*/
-public class AudioLoopbackLatencyActivity extends AudioLoopbackBaseActivity {
- private static final String TAG = AudioLoopbackLatencyActivity.class.getSimpleName();
+public class AudioLoopbackLatencyActivity extends PassFailButtons.Activity {
+ private static final String TAG = "AudioLoopbackLatencyActivity";
+
+ // JNI load
+ static {
+ try {
+ System.loadLibrary("audioloopback_jni");
+ } catch (UnsatisfiedLinkError e) {
+ Log.e(TAG, "Error loading Audio Loopback JNI library");
+ Log.e(TAG, "e: " + e);
+ e.printStackTrace();
+ }
+
+ /* TODO: gracefully fail/notify if the library can't be loaded */
+ }
+ protected AudioManager mAudioManager;
+
+ // UI
+ TextView mInputDeviceTxt;
+ TextView mOutputDeviceTxt;
+
+ TextView mAudioLevelText;
+ SeekBar mAudioLevelSeekbar;
+
+ TextView mTestPathTxt;
+
+ TextView mResultText;
+ ProgressBar mProgressBar;
+ int mMaxLevel;
+
+ OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
+ protected Button mTestButton;
+
+ String mYesString;
+ String mNoString;
+
+ // These flags determine the maximum allowed latency
+ private boolean mClaimsProAudio;
+ private boolean mClaimsOutput;
+ private boolean mClaimsInput;
+
+ // Useful info
+ private boolean mSupportsMMAP = AudioUtils.isMMapSupported();
+ private boolean mSupportsMMAPExclusive = AudioUtils.isMMapExclusiveSupported();
+
+ // Peripheral(s)
+ boolean mIsPeripheralAttached; // CDD ProAudio section C-1-3
+ AudioDeviceInfo mOutputDevInfo;
+ AudioDeviceInfo mInputDevInfo;
+
+ protected static final int TESTPERIPHERAL_INVALID = -1;
+ protected static final int TESTPERIPHERAL_NONE = 0;
+ protected static final int TESTPERIPHERAL_ANALOG_JACK = 1;
+ protected static final int TESTPERIPHERAL_USB = 2;
+ protected static final int TESTPERIPHERAL_DEVICE = 3; // device speaker + mic
+ protected int mTestPeripheral = TESTPERIPHERAL_NONE;
+
+ // Loopback Logic
+ NativeAnalyzerThread mNativeAnalyzerThread = null;
+
+ protected static final int NUM_TEST_PHASES = 5;
+ protected int mTestPhase = 0;
+
+ protected double[] mLatencyMillis = new double[NUM_TEST_PHASES];
+ protected double[] mConfidence = new double[NUM_TEST_PHASES];
+
+ protected double mMeanLatencyMillis;
+ protected double mMeanAbsoluteDeviation;
+ protected double mMeanConfidence;
+
+ protected static final double CONFIDENCE_THRESHOLD = 0.6;
+ // impossibly low latencies (indicating something in the test went wrong).
+ protected static final float EPSILON = 1.0f;
+ protected static final double PROAUDIO_RECOMMENDED_LATENCY_MS = 20.0;
+ protected static final double PROAUDIO_RECOMMENDED_USB_LATENCY_MS = 25.0;
+ protected static final double PROAUDIO_MUST_LATENCY_MS = 20.0;
+ protected static final double BASIC_RECOMMENDED_LATENCY_MS = 50.0;
+ protected static final double BASIC_MUST_LATENCY_MS = 800.0;
+ protected double mMustLatency;
+ protected double mRecommendedLatency;
+
+ // The audio stream callback threads should stop and close
+ // in less than a few hundred msec. This is a generous timeout value.
+ private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000;
+
+ //
+ // Common UI Handling
+ //
+ private void connectLoopbackUI() {
+ // Connected Device
+ mInputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackInputLbl));
+ mOutputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackOutputLbl));
+
+ mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text);
+ mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar);
+ mMaxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ mAudioLevelSeekbar.setMax(mMaxLevel);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0);
+ refreshLevel();
+
+ mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {}
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {}
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
+ progress, 0);
+ Log.i(TAG,"Level set to: " + progress);
+ refreshLevel();
+ }
+ });
+
+ mResultText = (TextView)findViewById(R.id.audio_loopback_results_text);
+ mProgressBar = (ProgressBar)findViewById(R.id.audio_loopback_progress_bar);
+ showWait(false);
+ }
+
+ //
+ // Peripheral Connection Logic
+ //
+ protected void scanPeripheralList(AudioDeviceInfo[] devices) {
+ // CDD Section C-1-3: USB port, host-mode support
+
+ // Can't just use the first record because then we will only get
+ // Source OR sink, not both even on devices that are both.
+ mOutputDevInfo = null;
+ mInputDevInfo = null;
+
+ // Any valid peripherals
+ // Do we leave in the Headset test to support a USB-Dongle?
+ for (AudioDeviceInfo devInfo : devices) {
+ if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE || // USB Peripheral
+ devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET || // USB dongle+LBPlug
+ devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET || // Loopback Plug?
+ devInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) { // Aux-cable loopback?
+ if (devInfo.isSink()) {
+ mOutputDevInfo = devInfo;
+ }
+ if (devInfo.isSource()) {
+ mInputDevInfo = devInfo;
+ }
+ } else {
+ handleDeviceConnection(devInfo);
+ }
+ }
+
+ // need BOTH input and output to test
+ mIsPeripheralAttached = mOutputDevInfo != null && mInputDevInfo != null;
+ calculateTestPeripheral();
+ showConnectedAudioPeripheral();
+ calculateLatencyThresholds();
+ displayLatencyThresholds();
+ }
+
+ protected void handleDeviceConnection(AudioDeviceInfo deviceInfo) {
+ // NOP
+ }
+
+ private class ConnectListener extends AudioDeviceCallback {
+ /*package*/ ConnectListener() {}
+
+ //
+ // AudioDevicesManager.OnDeviceConnectionListener
+ //
+ @Override
+ public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+ scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+ }
+
+ @Override
+ public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+ scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+ }
+ }
+
+ private void calculateTestPeripheral() {
+ if (!mIsPeripheralAttached) {
+ if ((mOutputDevInfo != null && mInputDevInfo == null)
+ || (mOutputDevInfo == null && mInputDevInfo != null)) {
+ mTestPeripheral = TESTPERIPHERAL_INVALID;
+ } else {
+ mTestPeripheral = TESTPERIPHERAL_DEVICE;
+ }
+ } else if (!areIODevicesOnePeripheral()) {
+ mTestPeripheral = TESTPERIPHERAL_INVALID;
+ } else if (mInputDevInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE ||
+ mInputDevInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET) {
+ mTestPeripheral = TESTPERIPHERAL_USB;
+ } else if (mInputDevInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
+ mInputDevInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) {
+ mTestPeripheral = TESTPERIPHERAL_ANALOG_JACK;
+ } else {
+ // Huh?
+ Log.e(TAG, "No valid peripheral found!?");
+ mTestPeripheral = TESTPERIPHERAL_NONE;
+ }
+ }
+
+ private boolean isPeripheralValidForTest() {
+ return mTestPeripheral == TESTPERIPHERAL_ANALOG_JACK
+ || mTestPeripheral == TESTPERIPHERAL_USB;
+ }
+
+ private void showConnectedAudioPeripheral() {
+ mInputDeviceTxt.setText(
+ mInputDevInfo != null ? mInputDevInfo.getProductName().toString()
+ : "Not connected");
+ mOutputDeviceTxt.setText(
+ mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString()
+ : "Not connected");
+
+ String pathName;
+ switch (mTestPeripheral) {
+ case TESTPERIPHERAL_INVALID:
+ pathName = "Invalid Test Peripheral";
+ break;
+
+ case TESTPERIPHERAL_ANALOG_JACK:
+ pathName = "Headset Jack";
+ break;
+
+ case TESTPERIPHERAL_USB:
+ pathName = "USB";
+ break;
+
+ case TESTPERIPHERAL_DEVICE:
+ pathName = "Device Speaker + Microphone";
+ break;
+
+ case TESTPERIPHERAL_NONE:
+ default:
+ pathName = "Error. Unknown Test Path";
+ break;
+ }
+ mTestPathTxt.setText(pathName);
+ mTestButton.setEnabled(
+ mTestPeripheral != TESTPERIPHERAL_INVALID && mTestPeripheral != TESTPERIPHERAL_NONE);
+
+ }
+
+ private boolean areIODevicesOnePeripheral() {
+ if (mOutputDevInfo == null || mInputDevInfo == null) {
+ return false;
+ }
+
+ return mOutputDevInfo.getProductName().toString().equals(
+ mInputDevInfo.getProductName().toString());
+ }
+
+ private void calculateLatencyThresholds() {
+ switch (mTestPeripheral) {
+ case TESTPERIPHERAL_ANALOG_JACK:
+ mRecommendedLatency = mClaimsProAudio
+ ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
+ mMustLatency = mClaimsProAudio
+ ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_MUST_LATENCY_MS;
+ break;
+
+ case TESTPERIPHERAL_USB:
+ mRecommendedLatency = mClaimsProAudio
+ ? PROAUDIO_RECOMMENDED_USB_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
+ mMustLatency = mClaimsProAudio
+ ? PROAUDIO_RECOMMENDED_USB_LATENCY_MS : BASIC_MUST_LATENCY_MS;
+ break;
+
+ case TESTPERIPHERAL_DEVICE:
+ // This isn't a valid case so we won't pass it, but it can be run
+ mRecommendedLatency = mClaimsProAudio
+ ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
+ mMustLatency = mClaimsProAudio
+ ? PROAUDIO_RECOMMENDED_LATENCY_MS :BASIC_MUST_LATENCY_MS;
+ break;
+
+ case TESTPERIPHERAL_NONE:
+ default:
+ mRecommendedLatency = BASIC_RECOMMENDED_LATENCY_MS;
+ mMustLatency = BASIC_MUST_LATENCY_MS;
+ break;
+ }
+ }
+
+ private void displayLatencyThresholds() {
+ if (isPeripheralValidForTest()) {
+ ((TextView) findViewById(R.id.audio_loopback_must_latency)).setText("" + mMustLatency);
+ ((TextView) findViewById(R.id.audio_loopback_recommended_latency)).setText(
+ "" + mRecommendedLatency);
+ } else {
+ String naStr = getResources().getString(R.string.audio_proaudio_NA);
+ ((TextView) findViewById(R.id.audio_loopback_must_latency)).setText(naStr);
+ ((TextView) findViewById(R.id.audio_loopback_recommended_latency)).setText(naStr);
+ }
+ }
+
+ /**
+ * refresh Audio Level seekbar and text
+ */
+ private void refreshLevel() {
+ int currentLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ mAudioLevelSeekbar.setProgress(currentLevel);
+
+ String levelText = String.format("%s: %d/%d",
+ getResources().getString(R.string.audio_loopback_level_text),
+ currentLevel, mMaxLevel);
+ mAudioLevelText.setText(levelText);
+ }
+
+ //
+ // show active progress bar
+ //
+ protected void showWait(boolean show) {
+ mProgressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ //
+ // Common logging
+ //
+ // Schema
+ private static final String KEY_LATENCY = "latency";
+ private static final String KEY_CONFIDENCE = "confidence";
+ private static final String KEY_SAMPLE_RATE = "sample_rate";
+ private static final String KEY_IS_PRO_AUDIO = "is_pro_audio";
+ private static final String KEY_IS_LOW_LATENCY = "is_low_latency";
+ private static final String KEY_IS_PERIPHERAL_ATTACHED = "is_peripheral_attached";
+ private static final String KEY_INPUT_PERIPHERAL_NAME = "input_peripheral";
+ private static final String KEY_OUTPUT_PERIPHERAL_NAME = "output_peripheral";
+ private static final String KEY_TEST_PERIPHERAL = "test_peripheral";
+ private static final String KEY_TEST_MMAP = "supports_mmap";
+ private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive";
+ private static final String KEY_LEVEL = "level";
+ private static final String KEY_BUFFER_SIZE = "buffer_size_in_frames";
+
+ @Override
+ public String getTestId() {
+ return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
+ }
+
+ @Override
+ public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; }
+
+ @Override
+ public final String getReportSectionName() {
+ return setTestNameSuffix(sCurrentDisplayMode, "audio_loopback_latency_activity");
+ }
+
+ //
+ // Subclasses should call this explicitly. SubClasses should call submit() after their logs
+ //
+ @Override
+ public void recordTestResults() {
+ if (mNativeAnalyzerThread == null) {
+ return; // no results to report
+ }
+
+ CtsVerifierReportLog reportLog = getReportLog();
+ reportLog.addValue(
+ KEY_LATENCY,
+ mMeanLatencyMillis,
+ ResultType.LOWER_BETTER,
+ ResultUnit.MS);
+
+ reportLog.addValue(
+ KEY_CONFIDENCE,
+ mMeanConfidence,
+ ResultType.HIGHER_BETTER,
+ ResultUnit.NONE);
+
+ reportLog.addValue(
+ KEY_SAMPLE_RATE,
+ mNativeAnalyzerThread.getSampleRate(),
+ ResultType.NEUTRAL,
+ ResultUnit.NONE);
+
+ reportLog.addValue(
+ KEY_IS_LOW_LATENCY,
+ mNativeAnalyzerThread.isLowLatencyStream(),
+ ResultType.NEUTRAL,
+ ResultUnit.NONE);
+
+ reportLog.addValue(
+ KEY_IS_PERIPHERAL_ATTACHED,
+ mIsPeripheralAttached,
+ ResultType.NEUTRAL,
+ ResultUnit.NONE);
+
+ reportLog.addValue(
+ KEY_IS_PRO_AUDIO,
+ mClaimsProAudio,
+ ResultType.NEUTRAL,
+ ResultUnit.NONE);
+
+ reportLog.addValue(
+ KEY_TEST_PERIPHERAL,
+ mTestPeripheral,
+ ResultType.NEUTRAL,
+ ResultUnit.NONE);
+
+ reportLog.addValue(
+ KEY_TEST_MMAP,
+ mSupportsMMAP,
+ ResultType.NEUTRAL,
+ ResultUnit.NONE);
+
+ reportLog.addValue(
+ KEY_TEST_MMAPEXCLUSIVE ,
+ mSupportsMMAPExclusive,
+ ResultType.NEUTRAL,
+ ResultUnit.NONE);
+
+ if (mIsPeripheralAttached) {
+ reportLog.addValue(
+ KEY_INPUT_PERIPHERAL_NAME,
+ mInputDevInfo != null ? mInputDevInfo.getProductName().toString() : "None",
+ ResultType.NEUTRAL,
+ ResultUnit.NONE);
+
+ reportLog.addValue(
+ KEY_OUTPUT_PERIPHERAL_NAME,
+ mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString() : "None",
+ ResultType.NEUTRAL,
+ ResultUnit.NONE);
+ }
+
+ int audioLevel = mAudioLevelSeekbar.getProgress();
+ reportLog.addValue(
+ KEY_LEVEL,
+ audioLevel,
+ ResultType.NEUTRAL,
+ ResultUnit.NONE);
+
+ reportLog.submit();
+ }
+
+ private void startAudioTest(Handler messageHandler) {
+ getPassButton().setEnabled(false);
+
+ mTestPhase = 0;
+ java.util.Arrays.fill(mLatencyMillis, 0.0);
+ java.util.Arrays.fill(mConfidence, 0.0);
+
+ mNativeAnalyzerThread = new NativeAnalyzerThread(this);
+ if (mNativeAnalyzerThread != null) {
+ mNativeAnalyzerThread.setMessageHandler(messageHandler);
+ // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
+ mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION);
+ startTestPhase();
+ } else {
+ Log.e(TAG, "Couldn't allocate native analyzer thread");
+ mResultText.setText(getResources().getString(R.string.audio_loopback_failure));
+ }
+ }
+
+ private void startTestPhase() {
+ if (mNativeAnalyzerThread != null) {
+ mNativeAnalyzerThread.startTest();
+
+ // what is this for?
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void handleTestCompletion() {
+ mMeanLatencyMillis = StatUtils.calculateMean(mLatencyMillis);
+ mMeanAbsoluteDeviation =
+ StatUtils.calculateMeanAbsoluteDeviation(mMeanLatencyMillis, mLatencyMillis);
+ mMeanConfidence = StatUtils.calculateMean(mConfidence);
+
+ boolean pass = isPeripheralValidForTest()
+ && mMeanConfidence >= CONFIDENCE_THRESHOLD
+ && mMeanLatencyMillis > EPSILON
+ && mMeanLatencyMillis < mMustLatency;
+
+ getPassButton().setEnabled(pass);
+
+ String result;
+ if (mMeanConfidence < CONFIDENCE_THRESHOLD) {
+ result = String.format(
+ "Test Finished\nInsufficient Confidence (%.2f < %.2f). No Results.",
+ mMeanConfidence, CONFIDENCE_THRESHOLD);
+ } else {
+ result = String.format(
+ "Test Finished - %s\nMean Latency:%.2f ms (required:%.2f)\n" +
+ "Mean Absolute Deviation: %.2f\n" +
+ " Confidence: %.2f\n" +
+ " Low Latency Path: %s",
+ (pass ? "PASS" : "FAIL"),
+ mMeanLatencyMillis,
+ mMustLatency,
+ mMeanAbsoluteDeviation,
+ mMeanConfidence,
+ mNativeAnalyzerThread.isLowLatencyStream() ? mYesString : mNoString);
+ }
+
+ // Make sure the test thread is finished. It should already be done.
+ if (mNativeAnalyzerThread != null) {
+ try {
+ mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ mResultText.setText(result);
+
+ recordTestResults();
+
+ showWait(false);
+ mTestButton.setEnabled(true);
+ }
+
+ private void handleTestPhaseCompletion() {
+ if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) {
+ mLatencyMillis[mTestPhase] = mNativeAnalyzerThread.getLatencyMillis();
+ mConfidence[mTestPhase] = mNativeAnalyzerThread.getConfidence();
+
+ String result = String.format(
+ "Test %d Finished\nLatency: %.2f ms\nConfidence: %.2f\n",
+ mTestPhase,
+ mLatencyMillis[mTestPhase],
+ mConfidence[mTestPhase]);
+
+ mResultText.setText(result);
+ try {
+ mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
+ // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ mTestPhase++;
+ if (mTestPhase >= NUM_TEST_PHASES) {
+ handleTestCompletion();
+ } else {
+ startTestPhase();
+ }
+ }
+ }
+
+ /**
+ * handler for messages from audio thread
+ */
+ private Handler mMessageHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ super.handleMessage(msg);
+ switch(msg.what) {
+ case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
+ Log.v(TAG,"got message native rec started!!");
+ showWait(true);
+ mResultText.setText(String.format("[phase: %d] - Test Running...",
+ (mTestPhase + 1)));
+ break;
+ case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR:
+ Log.v(TAG,"got message native rec can't start!!");
+ mResultText.setText("Test Error opening streams.");
+ handleTestCompletion();
+ break;
+ case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR:
+ Log.v(TAG,"got message native rec can't start!!");
+ mResultText.setText("Test Error while recording.");
+ handleTestCompletion();
+ break;
+ case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
+ mResultText.setText("Test FAILED due to errors.");
+ handleTestCompletion();
+ break;
+ case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING:
+ Log.i(TAG, "NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING");
+ mResultText.setText(String.format("[phase: %d] - Analyzing ...",
+ mTestPhase + 1));
+ break;
+ case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
+ Log.i(TAG, "NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE");
+ handleTestPhaseCompletion();
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.audio_loopback_latency_activity);
+
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+ setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1);
+
+ mClaimsOutput = AudioSystemFlags.claimsOutput(this);
+ mClaimsInput = AudioSystemFlags.claimsInput(this);
+ mClaimsProAudio = AudioSystemFlags.claimsProAudio(this);
+
+ mYesString = getResources().getString(R.string.audio_general_yes);
+ mNoString = getResources().getString(R.string.audio_general_no);
+
+ // Pro Audio
+ ((TextView)findViewById(R.id.audio_loopback_pro_audio)).setText(
+ "" + (mClaimsProAudio ? mYesString : mNoString));
+
+ // MMAP
+ ((TextView)findViewById(R.id.audio_loopback_mmap)).setText(
+ "" + (mSupportsMMAP ? mYesString : mNoString));
+ ((TextView)findViewById(R.id.audio_loopback_mmap_exclusive)).setText(
+ "" + (mSupportsMMAPExclusive ? mYesString : mNoString));
+
+ // Low Latency
+ ((TextView)findViewById(R.id.audio_loopback_low_latency)).setText(
+ "" + (AudioSystemFlags.claimsLowLatencyAudio(this) ? mYesString : mNoString));
+
+ mTestPathTxt = ((TextView)findViewById(R.id.audio_loopback_testpath));
+
+ mTestButton = (Button)findViewById(R.id.audio_loopback_test_btn);
+ mTestButton.setOnClickListener(mBtnClickListener);
+
+ mAudioManager = getSystemService(AudioManager.class);
+
+ mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
+
+ connectLoopbackUI();
+
+ calculateLatencyThresholds();
+ displayLatencyThresholds();
+ }
+
+ private class OnBtnClickListener implements OnClickListener {
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.audio_loopback_test_btn:
+ Log.i(TAG, "audio loopback test");
+ startAudioTest(mMessageHandler);
+ break;
+ }
+ }
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java
index 0e4f6da..29b1234 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java
@@ -16,22 +16,16 @@
package com.android.cts.verifier.audio;
-import com.android.cts.verifier.R;
-
import android.content.Context;
-
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
-
import android.os.Bundle;
-
import android.view.View;
-import android.view.View.OnClickListener;
-
-import android.widget.Button;
import android.widget.TextView;
+import com.android.cts.verifier.R;
+
/**
* Tests Audio Device Connection events for output devices by prompting the user to
* insert/remove a wired headset and noting the presence (or absence) of notifications.
@@ -41,13 +35,24 @@
TextView mConnectView;
TextView mDisconnectView;
- Button mClearMsgsBtn;
+ TextView mInfoView;
+
+ boolean mHandledInitialAddedMessage = false;
+ boolean mConnectReceived = false;
+ boolean mDisconnectReceived = false;
private class TestAudioDeviceCallback extends AudioDeviceCallback {
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+ // we will get this message when we setup the handler, so ignore the first one.
+ if (!mHandledInitialAddedMessage) {
+ mHandledInitialAddedMessage = true;
+ return;
+ }
if (addedDevices.length != 0) {
mConnectView.setText(
mContext.getResources().getString(R.string.audio_dev_notification_connectMsg));
+ mConnectReceived = true;
+ getPassButton().setEnabled(mConnectReceived && mDisconnectReceived);
}
}
@@ -56,35 +61,25 @@
mDisconnectView.setText(
mContext.getResources().getString(
R.string.audio_dev_notification_disconnectMsg));
+ mDisconnectReceived = true;
+ getPassButton().setEnabled(mConnectReceived && mDisconnectReceived);
}
}
}
@Override
- protected void enableTestButtons(boolean enabled) {
- // Nothing to do.
- }
-
- @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.audio_dev_notify);
mContext = this;
- mConnectView = (TextView)findViewById(R.id.audio_dev_notification_connect_msg);
- mDisconnectView = (TextView)findViewById(R.id.audio_dev_notification_disconnect_msg);
+ mConnectView = (TextView) findViewById(R.id.audio_dev_notification_connect_msg);
+ mDisconnectView = (TextView) findViewById(R.id.audio_dev_notification_disconnect_msg);
- ((TextView)findViewById(R.id.info_text)).setText(mContext.getResources().getString(
- R.string.audio_out_devices_notification_instructions));
-
- mClearMsgsBtn = (Button)findViewById(R.id.audio_dev_notification_connect_clearmsgs_btn);
- mClearMsgsBtn.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- mConnectView.setText("");
- mDisconnectView.setText("");
- }
- });
+ mInfoView = (TextView) findViewById(R.id.info_text);
+ mInfoView.setText(mContext.getResources().getString(
+ R.string.audio_devices_notification_instructions));
AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
audioManager.registerAudioDeviceCallback(new TestAudioDeviceCallback(), null);
@@ -94,4 +89,9 @@
setPassFailButtonClickListeners();
}
+
+ @Override
+ protected void enableTestButtons(boolean enabled) {
+ mInfoView.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
index 62749c1..b5d64ca 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
@@ -16,27 +16,19 @@
package com.android.cts.verifier.audio;
-import com.android.cts.verifier.R;
-
import android.content.Context;
-
-import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
import android.media.AudioTrack;
-
import android.os.Bundle;
import android.os.Handler;
-
import android.util.Log;
-
import android.view.View;
import android.view.View.OnClickListener;
-
import android.widget.Button;
import android.widget.TextView;
-import org.hyphonate.megaaudio.player.AudioSource;
+import com.android.cts.verifier.R;
+
import org.hyphonate.megaaudio.player.AudioSourceProvider;
import org.hyphonate.megaaudio.player.JavaPlayer;
import org.hyphonate.megaaudio.player.PlayerBuilder;
@@ -55,13 +47,21 @@
Button playBtn;
Button stopBtn;
+ TextView mInfoView;
+
+ int mNumRoutingNotifications;
private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
- int mNumTrackNotifications = 0;
+ // ignore messages sent as a consequence of starting the player
+ private static final int NUM_IGNORE_MESSAGES = 1;
// Mega Player
- JavaPlayer mAudioPlayer;
+ private JavaPlayer mAudioPlayer;
+ private AudioTrackRoutingChangeListener mRoutingChangeListener;
+ private boolean mIsPlaying;
+
+ private boolean mInitialRoutingMessageHandled;
private class OnBtnClickListener implements OnClickListener {
@Override
@@ -71,24 +71,53 @@
}
switch (v.getId()) {
case R.id.audio_routingnotification_playBtn:
- {
- mAudioPlayer.startStream();
- AudioTrack audioTrack = mAudioPlayer.getAudioTrack();
- audioTrack.addOnRoutingChangedListener(
- new AudioTrackRoutingChangeListener(), new Handler());
- }
+ startPlayback();
break;
case R.id.audio_routingnotification_playStopBtn:
- mAudioPlayer.stopStream();
+ stopPlayback();
break;
}
}
}
+ private void startPlayback() {
+ if (!mIsPlaying) {
+ mNumRoutingNotifications = 0;
+
+ mAudioPlayer.startStream();
+
+ AudioTrack audioTrack = mAudioPlayer.getAudioTrack();
+ audioTrack.addOnRoutingChangedListener(mRoutingChangeListener,
+ new Handler());
+
+ mIsPlaying = true;
+
+ enablePlayButtons(!mIsPlaying, mIsPlaying);
+ }
+ }
+
+ private void stopPlayback() {
+ if (mIsPlaying) {
+ mAudioPlayer.stopStream();
+
+ AudioTrack audioTrack = mAudioPlayer.getAudioTrack();
+ audioTrack.removeOnRoutingChangedListener(mRoutingChangeListener);
+
+ mIsPlaying = false;
+
+ enablePlayButtons(!mIsPlaying, mIsPlaying);
+ }
+ }
+
private class AudioTrackRoutingChangeListener implements AudioTrack.OnRoutingChangedListener {
public void onRoutingChanged(AudioTrack audioTrack) {
- mNumTrackNotifications++;
+ // Starting playback triggers a messages, so ignore the first one.
+ mNumRoutingNotifications++;
+ if (mNumRoutingNotifications <= NUM_IGNORE_MESSAGES) {
+ return;
+ }
+
TextView textView =
(TextView)findViewById(R.id.audio_routingnotification_audioTrack_change);
String msg = mContext.getResources().getString(
@@ -98,14 +127,21 @@
int deviceType = routedDevice != null ? routedDevice.getType() : -1;
textView.setText(msg + " - " +
deviceName + " [0x" + Integer.toHexString(deviceType) + "]" +
- " - " + mNumTrackNotifications);
+ " - " + mNumRoutingNotifications);
+ getPassButton().setEnabled(true);
}
}
@Override
protected void enableTestButtons(boolean enabled) {
- playBtn.setEnabled(enabled);
- stopBtn.setEnabled(enabled);
+ enablePlayButtons(!mIsPlaying, mIsPlaying);
+
+ mInfoView.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ private void enablePlayButtons(boolean enablePlay, boolean enableStop) {
+ playBtn.setEnabled(enablePlay);
+ stopBtn.setEnabled(enableStop);
}
@Override
@@ -115,11 +151,15 @@
mContext = this;
- playBtn = (Button)findViewById(R.id.audio_routingnotification_playBtn);
+ playBtn = (Button) findViewById(R.id.audio_routingnotification_playBtn);
playBtn.setOnClickListener(mBtnClickListener);
- stopBtn = (Button)findViewById(R.id.audio_routingnotification_playStopBtn);
+ stopBtn = (Button) findViewById(R.id.audio_routingnotification_playStopBtn);
stopBtn.setOnClickListener(mBtnClickListener);
+ enablePlayButtons(false, false);
+
+ mInfoView = (TextView) findViewById(R.id.info_text);
+
// Setup Player
//
// Allocate the source provider for the sort of signal we want to play
@@ -139,17 +179,16 @@
Log.e(TAG, "Failed MegaPlayer build.");
}
+ mRoutingChangeListener = new AudioTrackRoutingChangeListener();
+
// "Honor System" buttons
super.setup();
-
setPassFailButtonClickListeners();
}
@Override
public void onBackPressed () {
- if (mAudioPlayer != null) {
- mAudioPlayer.stopStream();
- }
+ stopPlayback();
super.onBackPressed();
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java
index 2e308f2..97878f4 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java
@@ -16,31 +16,24 @@
package com.android.cts.verifier.audio;
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import com.android.compatibility.common.util.ReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-
-import android.content.Context;
-
-import android.os.Bundle;
-import android.os.Handler;
-
import android.util.Log;
-
import android.view.View;
import android.view.View.OnClickListener;
-
import android.widget.Button;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
abstract class AudioWiredDeviceBaseActivity extends PassFailButtons.Activity {
private static final String TAG = AudioWiredDeviceBaseActivity.class.getSimpleName();
private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
- abstract protected void enableTestButtons(boolean enabled);
+ protected void enableTestButtons(boolean enabled) {
+ // NOP by default
+ }
private void recordWiredPortFound(boolean found) {
getReportLog().addValue(
@@ -56,6 +49,8 @@
((Button)findViewById(R.id.audio_wired_yes)).setOnClickListener(mBtnClickListener);
enableTestButtons(false);
+
+ getPassButton().setEnabled(false);
}
private class OnBtnClickListener implements OnClickListener {
@@ -66,12 +61,14 @@
Log.i(TAG, "User denies wired device existence");
enableTestButtons(false);
recordWiredPortFound(false);
+ getPassButton().setEnabled(true);
break;
case R.id.audio_wired_yes:
Log.i(TAG, "User confirms wired device existence");
enableTestButtons(true);
recordWiredPortFound(true);
+ getPassButton().setEnabled(false);
break;
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
index d32749f..9804cd3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
@@ -67,6 +67,7 @@
private static final String INFO_DIALOG_MESSAGE_ID = "infoDialogMessageId";
// ReportLog Schema
+ private static final String SECTION_PRO_AUDIO_ACTIVITY = "pro_audio_activity";
private static final String KEY_CLAIMS_PRO = "claims_pro_audio";
private static final String KEY_CLAIMS_LOW_LATENCY = "claims_low_latency_audio";
private static final String KEY_CLAIMS_MIDI = "claims_midi";
@@ -232,6 +233,14 @@
// PassFailButtons Overrides
//
@Override
+ public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; }
+
+ @Override
+ public final String getReportSectionName() {
+ return setTestNameSuffix(sCurrentDisplayMode, SECTION_PRO_AUDIO_ACTIVITY);
+ }
+
+ @Override
public void recordTestResults() {
CtsVerifierReportLog reportLog = getReportLog();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/PhotoCaptureActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/PhotoCaptureActivity.java
index 6c8c501..406a305 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/PhotoCaptureActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/PhotoCaptureActivity.java
@@ -23,6 +23,7 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
+import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.ShutterCallback;
@@ -34,8 +35,7 @@
import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
+import android.view.TextureView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
@@ -59,22 +59,24 @@
* An activity for showing the camera preview and taking a picture.
*/
public class PhotoCaptureActivity extends Activity
- implements PictureCallback, SurfaceHolder.Callback {
+ implements PictureCallback, TextureView.SurfaceTextureListener {
private static final String TAG = PhotoCaptureActivity.class.getSimpleName();
private static final int FOV_REQUEST_CODE = 1006;
private static final String PICTURE_FILENAME = "photo.jpg";
private static float mReportedFovDegrees = 0;
private float mReportedFovPrePictureTaken = -1;
- private SurfaceView mPreview;
- private SurfaceHolder mSurfaceHolder;
+ private TextureView mPreviewView;
+ private SurfaceTexture mPreviewTexture;
+ private int mPreviewTexWidth;
+ private int mPreviewTexHeight;
+
private Spinner mResolutionSpinner;
private List<SelectableResolution> mSupportedResolutions;
private ArrayAdapter<SelectableResolution> mAdapter;
private SelectableResolution mSelectedResolution;
private Camera mCamera;
- private Size mSurfaceSize;
private boolean mCameraInitialized = false;
private boolean mPreviewActive = false;
private boolean mTakingPicture = false;
@@ -114,12 +116,8 @@
}
}
- mPreview = (SurfaceView) findViewById(R.id.camera_fov_camera_preview);
- mSurfaceHolder = mPreview.getHolder();
- mSurfaceHolder.addCallback(this);
-
- // This is required for older versions of Android hardware.
- mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+ mPreviewView = (TextureView) findViewById(R.id.camera_fov_camera_preview);
+ mPreviewView.setSurfaceTextureListener(this);
TextView textView = (TextView) findViewById(R.id.camera_fov_tap_to_take_photo);
textView.setTextColor(Color.WHITE);
@@ -365,20 +363,27 @@
}
@Override
- public void surfaceChanged(
- SurfaceHolder holder, int format, int width, int height) {
- mSurfaceSize = new Size(width, height);
+ public void onSurfaceTextureAvailable(SurfaceTexture surface,
+ int width, int height) {
+ mPreviewTexture = surface;
+ mPreviewTexWidth = width;
+ mPreviewTexHeight = height;
initializeCamera();
}
@Override
- public void surfaceCreated(SurfaceHolder holder) {
- // Nothing to do.
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ // Ignored, Camera does all the work for us
}
@Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- // Nothing to do.
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ // Invoked every time there's a new Camera preview frame
}
private void showNextDialogToChoosePreviewSize() {
@@ -432,14 +437,14 @@
}
private void initializeCamera(boolean startPreviewAfterInit) {
- if (mCamera == null || mSurfaceHolder.getSurface() == null) {
+ if (mCamera == null || mPreviewTexture == null) {
return;
}
try {
- mCamera.setPreviewDisplay(mSurfaceHolder);
+ mCamera.setPreviewTexture(mPreviewTexture);
} catch (Throwable t) {
- Log.e(TAG, "Could not set preview display", t);
+ Log.e(TAG, "Could not set preview texture", t);
Toast.makeText(this, t.getMessage(), Toast.LENGTH_LONG).show();
return;
}
@@ -452,13 +457,13 @@
Size selectedPreviewSize = null;
if (mPreviewSizes != null) {
selectedPreviewSize = mPreviewSizes[mSelectedResolution.cameraId];
- } else if (mSurfaceSize != null) {
+ } else {
if (mPreviewOrientation == 0 || mPreviewOrientation == 180) {
selectedPreviewSize = getBestPreviewSize(
- mSurfaceSize.width, mSurfaceSize.height, params);
+ mPreviewTexWidth, mPreviewTexHeight, params);
} else {
selectedPreviewSize = getBestPreviewSize(
- mSurfaceSize.height, mSurfaceSize.width, params);
+ mPreviewTexHeight, mPreviewTexWidth, params);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java
index 7eae01c..924912f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java
@@ -15,8 +15,11 @@
*/
package com.android.cts.verifier.camera.intents;
+import static android.media.MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO;
+import static android.media.MediaMetadataRetriever.METADATA_KEY_LOCATION;
+
+import android.Manifest;
import android.app.job.JobInfo;
-import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -24,7 +27,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.content.pm.PermissionInfo;
import android.hardware.Camera;
import android.media.ExifInterface;
import android.media.MediaMetadataRetriever;
@@ -39,24 +41,20 @@
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
-import androidx.core.content.FileProvider;
-import android.Manifest;
+import android.widget.Toast;
-import com.android.cts.verifier.camera.intents.CameraContentJobService;
+import androidx.core.content.FileProvider;
+
import com.android.cts.verifier.PassFailButtons;
import com.android.cts.verifier.R;
import com.android.cts.verifier.TestResult;
-import android.widget.Toast;
-
-import static android.media.MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO;
-import static android.media.MediaMetadataRetriever.METADATA_KEY_LOCATION;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
-import java.util.TreeSet;
-import java.util.Date;
import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TreeSet;
/**
* Tests for manual verification of uri trigger and camera intents being fired.
@@ -518,7 +516,11 @@
mediaRetriever.extractMetadata(METADATA_KEY_HAS_VIDEO) +
" METADATA_KEY_LOCATION: " +
mediaRetriever.extractMetadata(METADATA_KEY_LOCATION));
- mediaRetriever.release();
+ try {
+ mediaRetriever.release();
+ } catch (IOException e) {
+ // We ignore errors occurred while releasing the MediaMetadataRetriever.
+ }
/* successful, unless we get the URI trigger back at some point later on. */
mActionSuccess = true;
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
index 63d9687..1651ec5 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
@@ -150,8 +150,6 @@
// Performance class R version number
private static final int PERFORMANCE_CLASS_R = Build.VERSION_CODES.R;
- // Performance class S version number
- private static final int PERFORMANCE_CLASS_S = Build.VERSION_CODES.R + 1;
public static final int SERVERPORT = 6000;
@@ -167,6 +165,7 @@
public static final String TRIGGER_AF_KEY = "af";
public static final String VIB_PATTERN_KEY = "pattern";
public static final String EVCOMP_KEY = "evComp";
+ public static final String AUTO_FLASH_KEY = "autoFlash";
public static final String AUDIO_RESTRICTION_MODE_KEY = "mode";
private CameraManager mCameraManager = null;
@@ -737,9 +736,11 @@
doCheckStreamCombination(cmdObj);
} else if ("isCameraPrivacyModeSupported".equals(cmdObj.getString("cmdName"))) {
doCheckCameraPrivacyModeSupport();
- } else if ("isPerformanceClassPrimaryCamera".equals(cmdObj.getString("cmdName"))) {
+ } else if ("isPrimaryCamera".equals(cmdObj.getString("cmdName"))) {
String cameraId = cmdObj.getString("cameraId");
- doCheckPerformanceClassPrimaryCamera(cameraId);
+ doCheckPrimaryCamera(cameraId);
+ } else if ("isPerformanceClass".equals(cmdObj.getString("cmdName"))) {
+ doCheckPerformanceClass();
} else if ("measureCameraLaunchMs".equals(cmdObj.getString("cmdName"))) {
String cameraId = cmdObj.getString("cameraId");
doMeasureCameraLaunchMs(cameraId);
@@ -1082,10 +1083,7 @@
hasPrivacySupport ? "true" : "false");
}
- private void doCheckPerformanceClassPrimaryCamera(String cameraId) throws ItsException {
- boolean isPerfClass = (Build.VERSION.MEDIA_PERFORMANCE_CLASS == PERFORMANCE_CLASS_S
- || Build.VERSION.MEDIA_PERFORMANCE_CLASS == PERFORMANCE_CLASS_R);
-
+ private void doCheckPrimaryCamera(String cameraId) throws ItsException {
if (mItsCameraIdList == null) {
mItsCameraIdList = ItsUtils.getItsCompatibleCameraIds(mCameraManager);
}
@@ -1116,8 +1114,15 @@
throw new ItsException("Failed to get camera characteristics", e);
}
- mSocketRunnableObj.sendResponse("performanceClassPrimaryCamera",
- (isPerfClass && isPrimaryCamera) ? "true" : "false");
+ mSocketRunnableObj.sendResponse("primaryCamera",
+ isPrimaryCamera ? "true" : "false");
+ }
+
+ private void doCheckPerformanceClass() throws ItsException {
+ boolean isPerfClass = (Build.VERSION.MEDIA_PERFORMANCE_CLASS >= PERFORMANCE_CLASS_R);
+
+ mSocketRunnableObj.sendResponse("performanceClass",
+ isPerfClass ? "true" : "false");
}
private double invokeCameraPerformanceTest(Class testClass, String testName,
@@ -1289,6 +1294,12 @@
Logt.i(TAG, String.format("Running 3A with AE exposure compensation value: %d", evComp));
}
+ // Auto flash can be specified as part of AE convergence.
+ boolean autoFlash = params.optBoolean(AUTO_FLASH_KEY, false);
+ if (autoFlash == true) {
+ Logt.i(TAG, String.format("Running with auto flash mode."));
+ }
+
// By default, AE and AF both get triggered, but the user can optionally override this.
// Also, AF won't get triggered if the lens is fixed-focus.
boolean doAE = true;
@@ -1362,8 +1373,6 @@
req.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
req.set(CaptureRequest.CONTROL_CAPTURE_INTENT,
CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW);
- req.set(CaptureRequest.CONTROL_AE_MODE,
- CaptureRequest.CONTROL_AE_MODE_ON);
req.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 0);
req.set(CaptureRequest.CONTROL_AE_LOCK, false);
req.set(CaptureRequest.CONTROL_AE_REGIONS, regionAE);
@@ -1382,6 +1391,14 @@
req.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, evComp);
}
+ if (autoFlash == false) {
+ req.set(CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_ON);
+ } else {
+ req.set(CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+ }
+
if (mConvergedAE && mNeedsLockedAE) {
req.set(CaptureRequest.CONTROL_AE_LOCK, true);
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
index 08cd8b2..0f75511 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
@@ -41,6 +41,9 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileNotFoundException;
@@ -77,6 +80,17 @@
Arrays.asList(new String[] {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}));
private static final int MAX_SUMMARY_LEN = 200;
+ private static final int MPC12_CAMERA_LAUNCH_THRESHOLD = 600; // ms
+ private static final int MPC12_JPEG_CAPTURE_THRESHOLD = 1000; // ms
+
+ private static final String MPC_TESTS_REPORT_LOG_NAME = "MediaPerformanceClassLogs";
+ private static final String MPC_TESTS_REPORT_LOG_SECTION = "CameraIts";
+
+ private static final Pattern MPC12_CAMERA_LAUNCH_PATTERN =
+ Pattern.compile("camera_launch_time_ms:(\\d+(\\.\\d+)?)");
+ private static final Pattern MPC12_JPEG_CAPTURE_PATTERN =
+ Pattern.compile("1080p_jpeg_capture_time_ms:(\\d+(\\.\\d+)?)");
+
private final ResultReceiver mResultsReceiver = new ResultReceiver();
private boolean mReceiverRegistered = false;
@@ -97,7 +111,6 @@
add("scene4");
add("scene5");
add("scene6");
- add("scene_change");
add("sensor_fusion");
}};
// This must match scenes of SUB_CAMERA_TESTS in tools/run_all_tests.py
@@ -116,6 +129,10 @@
private final HashMap<ResultKey, Boolean> mExecutedScenes = new HashMap<>();
// map camera id to ITS summary report path
private final HashMap<ResultKey, String> mSummaryMap = new HashMap<>();
+ // All primary cameras for which MPC level test has run
+ private Set<ResultKey> mExecutedMpcTests = null;
+ // Map primary camera id to MPC level
+ private final HashMap<String, Integer> mMpcLevelMap = new HashMap<>();
final class ResultKey {
public final String cameraId;
@@ -213,7 +230,6 @@
// Update test execution results
for (String scene : scenes) {
- HashMap<String, String> executedTests = new HashMap<>();
JSONObject sceneResult = jsonResults.getJSONObject(scene);
Log.v(TAG, sceneResult.toString());
String result = sceneResult.getString("result");
@@ -241,6 +257,22 @@
mSummaryMap.put(key, summary);
}
} // do nothing for NOT_EXECUTED scenes
+
+ if (sceneResult.isNull("mpc_metrics")) {
+ continue;
+ }
+ // Update MPC level
+ JSONArray metrics = sceneResult.getJSONArray("mpc_metrics");
+ for (int i = 0; i < metrics.length(); i++) {
+ String mpcResult = metrics.getString(i);
+ if (!matchMpcResult(cameraId, mpcResult, MPC12_CAMERA_LAUNCH_PATTERN,
+ "2.2.7.2/7.5/H-1-6", MPC12_CAMERA_LAUNCH_THRESHOLD) &&
+ !matchMpcResult(cameraId, mpcResult, MPC12_JPEG_CAPTURE_PATTERN,
+ "2.2.7.2/7.5/H-1-5", MPC12_JPEG_CAPTURE_THRESHOLD)) {
+ Log.e(TAG, "Error parsing MPC result string:" + mpcResult);
+ return;
+ }
+ }
}
} catch (org.json.JSONException e) {
Log.e(TAG, "Error reading json result string:" + results , e);
@@ -249,6 +281,7 @@
// Set summary if all scenes reported
if (mSummaryMap.keySet().containsAll(mAllScenes)) {
+ // Save test summary
StringBuilder summary = new StringBuilder();
for (String path : mSummaryMap.values()) {
appendFileContentToSummary(summary, path);
@@ -260,6 +293,17 @@
summary.toString(), 1.0, ResultType.NEUTRAL, ResultUnit.NONE);
}
+ // Save MPC info once both front primary and rear primary data are collected.
+ if (mExecutedMpcTests.size() == 4) {
+ ItsTestActivity.this.getReportLog().addValue(
+ "Version", "0.0.1", ResultType.NEUTRAL, ResultUnit.NONE);
+ for (Map.Entry<String, Integer> entry : mMpcLevelMap.entrySet()) {
+ ItsTestActivity.this.getReportLog().addValue(entry.getKey(),
+ entry.getValue(), ResultType.NEUTRAL, ResultUnit.NONE);
+ }
+ ItsTestActivity.this.getReportLog().submit();
+ }
+
// Display current progress
StringBuilder progress = new StringBuilder();
for (ResultKey k : mAllScenes) {
@@ -321,6 +365,29 @@
}
}
}
+
+ private boolean matchMpcResult(String cameraId, String mpcResult, Pattern pattern,
+ String reqNum, float threshold) {
+ Matcher matcher = pattern.matcher(mpcResult);
+ boolean match = matcher.matches();
+
+ if (match) {
+ // Store test result
+ ItsTestActivity.this.getReportLog().addValue("Cam" + cameraId,
+ mpcResult, ResultType.NEUTRAL, ResultUnit.NONE);
+
+ float latency = Float.parseFloat(matcher.group(1));
+ int mpcLevel = latency < threshold ? 31 : 0;
+ mExecutedMpcTests.add(new ResultKey(cameraId, reqNum));
+
+ if (mMpcLevelMap.containsKey(reqNum)) {
+ mpcLevel = Math.min(mpcLevel, mMpcLevelMap.get(reqNum));
+ }
+ mMpcLevelMap.put(reqNum, mpcLevel);
+ }
+
+ return match;
+ }
}
@Override
@@ -388,6 +455,9 @@
testTitle(cam, scene),
testId(cam, scene)));
}
+ if (mExecutedMpcTests == null) {
+ mExecutedMpcTests = new TreeSet<>(mComparator);
+ }
Log.d(TAG,"Total combinations to test on this device:" + mAllScenes.size());
}
}
@@ -427,4 +497,14 @@
setInfoResources(R.string.camera_its_test, R.string.camera_its_test_info, -1);
setPassFailButtonClickListeners();
}
+
+ @Override
+ public String getReportFileName() {
+ return MPC_TESTS_REPORT_LOG_NAME;
+ }
+
+ @Override
+ public String getReportSectionName() {
+ return MPC_TESTS_REPORT_LOG_SECTION;
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeChecker.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeChecker.java
index a7f742f..06e5ea3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeChecker.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeChecker.java
@@ -29,11 +29,11 @@
import android.os.Handler;
import android.os.Message;
import android.os.PersistableBundle;
+import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
import java.lang.ref.WeakReference;
-import java.util.List;
public final class GarageModeChecker extends JobService {
private static final String TAG = GarageModeChecker.class.getSimpleName();
@@ -45,6 +45,7 @@
static final String PREFS_TERMINATION = "termination-time";
static final String PREFS_JOB_UPDATE = "job-update-time";
static final String PREFS_HAD_CONNECTIVITY = "had-connectivity";
+ static final String PREFS_START_BOOT_COUNT = "start-boot-count";
static final int SECONDS_PER_ITERATION = 10;
static final int MS_PER_ITERATION = SECONDS_PER_ITERATION * 1000;
@@ -172,6 +173,17 @@
return true;
}
+ static int getBootCount(Context context) {
+ int bootCount = 0;
+ try {
+ bootCount = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.BOOT_COUNT);
+ } catch (Settings.SettingNotFoundException e) {
+ Log.e(TAG, "Could not get Settings.Global.BOOT_COUNT: ", e);
+ }
+ return bootCount;
+ }
+
private final class GarageModeCheckerTask extends AsyncTask<Void, Void, Boolean> {
private final WeakReference<Handler> mHandler;
private final JobParameters mJobParameter;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeTestActivity.java
index ac13ba2..a4a2930 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeTestActivity.java
@@ -90,6 +90,8 @@
editor.putLong(GarageModeChecker.PREFS_GARAGE_MODE_END, 0);
editor.putLong(GarageModeChecker.PREFS_TERMINATION, 0);
editor.putBoolean(GarageModeChecker.PREFS_HAD_CONNECTIVITY, false);
+ editor.putLong(GarageModeChecker.PREFS_START_BOOT_COUNT,
+ GarageModeChecker.getBootCount(context));
editor.commit();
GarageModeChecker.scheduleAnIdleJob(context, NUM_SECONDS_DURATION);
@@ -131,6 +133,8 @@
long termination = prefs.getLong(GarageModeChecker.PREFS_TERMINATION, 0);
long jobUpdate = prefs.getLong(GarageModeChecker.PREFS_JOB_UPDATE, 0);
boolean hadConnectivity = prefs.getBoolean(GarageModeChecker.PREFS_HAD_CONNECTIVITY, false);
+ long startBootCount = prefs.getLong(GarageModeChecker.PREFS_START_BOOT_COUNT, 0);
+ long currentBootCount = GarageModeChecker.getBootCount(context);
boolean testPassed = false;
if (initiateTime == 0) {
@@ -185,6 +189,12 @@
(hadConnectivity ? "" : "not "),
dateTime.format(initiateTime), dateTime.format(garageModeStart),
dateTime.format(termination));
+ } else if ((garageModeStart > 0 && garageModeEnd == 0)
+ && (currentBootCount - startBootCount > 0)) {
+ resultsString = "Test failed.\n\n"
+ + "Garage Mode started, but system restarted before it completed.\n\n"
+ + dateTime.format(initiateTime) + " -- Test was enabled\n"
+ + dateTime.format(garageModeStart) + " -- Garage mode started";
} else if (now < jobUpdate + GarageModeChecker.MS_PER_ITERATION * 2) {
resultsString = "Garage Mode started and test is running.\n\n"
+ dateTime.format(initiateTime) + " -- Test was enabled\n"
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/GearSelectionTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/GearSelectionTestActivity.java
index c926d1a..558c411 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/car/GearSelectionTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/GearSelectionTestActivity.java
@@ -43,7 +43,6 @@
private static final long TEST_TIMEOUT_MINUTES = 10;
private List<Integer> mSupportedGears;
- private Integer mGearsAchievedCount = 0;
private TextView mExpectedGearSelectionTextView;
private TextView mCurrentGearSelectionTextView;
private CarPropertyManager mCarPropertyManager;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/deskclock/DeskClockTestsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/deskclock/DeskClockTestsActivity.java
index aaea279..2cafd94 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/deskclock/DeskClockTestsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/deskclock/DeskClockTestsActivity.java
@@ -6,15 +6,16 @@
import android.database.DataSetObserver;
import android.os.Bundle;
import android.provider.AlarmClock;
+import android.util.Log;
import com.android.cts.verifier.ArrayTestListAdapter;
import com.android.cts.verifier.IntentDrivenTestActivity;
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-import com.android.cts.verifier.TestListAdapter.TestListItem;
import com.android.cts.verifier.IntentDrivenTestActivity.ButtonInfo;
import com.android.cts.verifier.IntentDrivenTestActivity.IntentFactory;
import com.android.cts.verifier.IntentDrivenTestActivity.TestInfo;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestListAdapter.TestListItem;
import java.util.ArrayList;
import java.util.Calendar;
@@ -24,6 +25,8 @@
*/
public class DeskClockTestsActivity extends PassFailButtons.TestListActivity {
+ private static final String TAG = DeskClockTestsActivity.class.getSimpleName();
+
private static final String SHOW_ALARMS_TEST = "SHOW_ALARMS";
public static final String SET_ALARM_WITH_UI_TEST = "SET_ALARM_WITH_UI";
public static final String START_ALARM_TEST = "START_ALARM";
@@ -153,15 +156,13 @@
private void addTests(ArrayTestListAdapter adapter, TestInfo[] tests) {
for (TestInfo info : tests) {
-
- final int title = info.getTitle();
- adapter.add(TestListItem.newTest(this, title, info.getTestId(),
- new Intent(this, IntentDrivenTestActivity.class)
- .putExtra(IntentDrivenTestActivity.EXTRA_ID, info.getTestId())
- .putExtra(IntentDrivenTestActivity.EXTRA_TITLE, title)
- .putExtra(IntentDrivenTestActivity.EXTRA_INFO, info.getInfoText())
- .putExtra(IntentDrivenTestActivity.EXTRA_BUTTONS, info.getButtons()),
- null));
+ int title = info.getTitle();
+ String testId = info.getTestId();
+ Intent intent = IntentDrivenTestActivity.newIntent(this, testId, title,
+ info.getInfoText(), info.getButtons());
+ Log.d(TAG, "Adding test with " + IntentDrivenTestActivity.toString(this, intent));
+ adapter.add(TestListItem.newTest(this, title, testId, intent,
+ /* applicableFeatures= */ null));
}
}
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 218897f..489336f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -382,11 +382,29 @@
uninstallHelperPackage();
} break;
case COMMAND_SET_PERMISSION_GRANT_STATE: {
- Log.d(TAG, "Granting permission using " + mDpm);
- mDpm.setPermissionGrantState(mAdmin, getPackageName(),
- intent.getStringExtra(EXTRA_PERMISSION),
- intent.getIntExtra(EXTRA_GRANT_STATE,
- DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT));
+ String pkgName = getPackageName();
+ String permission = intent.getStringExtra(EXTRA_PERMISSION);
+ int grantState = intent.getIntExtra(EXTRA_GRANT_STATE,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+ String action;
+ switch (grantState) {
+ case DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED:
+ action = "Granting " + permission;
+ break;
+ case DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED:
+ action = "Denying " + permission;
+ break;
+ case DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT:
+ action = "Setting " + permission + " to default state";
+ break;
+ default:
+ action = "Setting grantState of " + permission + " to " + grantState;
+ }
+ Log.d(TAG, action + " to " + pkgName + " using " + mDpm);
+ int stateBefore = mDpm.getPermissionGrantState(mAdmin, pkgName, permission);
+ mDpm.setPermissionGrantState(mAdmin, pkgName, permission, grantState);
+ int stateAfter = mDpm.getPermissionGrantState(mAdmin, pkgName, permission);
+ Log.d(TAG, "Grant state: before=" + stateBefore + ", after=" + stateAfter);
} break;
case COMMAND_ADD_PERSISTENT_PREFERRED_ACTIVITIES: {
final ComponentName componentName =
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/IntentFiltersTestHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/IntentFiltersTestHelper.java
index 70aaab5..09c1c9c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/IntentFiltersTestHelper.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/IntentFiltersTestHelper.java
@@ -16,34 +16,27 @@
package com.android.cts.verifier.managedprovisioning;
-import android.app.Activity;
-import android.app.admin.DevicePolicyManager;
import android.app.DownloadManager;
-import android.content.ComponentName;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.media.audiofx.AudioEffect;
import android.net.Uri;
import android.nfc.cardemulation.CardEmulation;
import android.os.Build;
-import android.os.Bundle;
import android.os.Environment;
-import android.os.UserHandle;
import android.os.UserManager;
import android.provider.AlarmClock;
import android.provider.CalendarContract.Events;
import android.provider.MediaStore;
import android.provider.Settings;
-import android.speech.RecognizerIntent;
import android.util.Log;
-import android.widget.Toast;
-import java.util.Arrays;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -161,7 +154,7 @@
PackageManager pm = mContext.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
- && pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) {
+ && pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)) {
forwardedIntentsFromManaged.addAll(Arrays.asList(
new Intent("android.intent.action.CALL_EMERGENCY").setData(
Uri.parse("tel:123")),
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 0ffd259..e91f92b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -39,6 +39,7 @@
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
+import android.app.PendingIntent;
import android.app.Person;
import android.content.Context;
import android.content.Intent;
@@ -122,6 +123,12 @@
tests.add(new IsEnabledTest());
tests.add(new ServiceStartedTest());
tests.add(new NotificationReceivedTest());
+ if (!isAutomotive) {
+ tests.add(new SendUserToChangeFilter());
+ tests.add(new AskIfFilterChanged());
+ tests.add(new NotificationTypeFilterTest());
+ tests.add(new ResetChangeFilter());
+ }
tests.add(new LongMessageTest());
tests.add(new DataIntactTest());
tests.add(new AudiblyAlertedTest());
@@ -146,6 +153,7 @@
tests.add(new RequestBindTest());
tests.add(new MessageBundleTest());
tests.add(new ConversationOrderingTest());
+ tests.add(new HunDisplayTest());
tests.add(new EnableHintsTest());
tests.add(new IsDisabledTest());
tests.add(new ServiceStoppedTest());
@@ -1694,6 +1702,114 @@
}
}
+ /**
+ * Tests that heads-up notifications appear with the view, resources, and actions provided
+ * in Notification.Builder.
+ */
+ private class HunDisplayTest extends InteractiveTestCase {
+
+ @Override
+ protected void setUp() {
+ createChannels();
+ sendNotifications();
+ status = READY;
+ }
+
+ @Override
+ protected void tearDown() {
+ mNm.cancelAll();
+ deleteChannels();
+ delay();
+ }
+
+ @Override
+ protected View inflate(ViewGroup parent) {
+ return createPassFailItem(parent, R.string.hun_display);
+ }
+
+ private void sendNotifications() {
+ mTag1 = UUID.randomUUID().toString();
+ mId1 = NOTIFICATION_ID + 1;
+
+ Notification n1 = new Notification.Builder(mContext, NOISY_NOTIFICATION_CHANNEL_ID)
+ .setContentTitle("HunDisplayTest")
+ .setContentText(mTag1)
+ .setSmallIcon(R.drawable.ic_stat_alice)
+ .setLargeIcon(Icon.createWithResource(mContext, R.drawable.test_pass_gradient))
+ .addAction(generateAction(1))
+ .addAction(generateAction(2))
+ .build();
+ mNm.notify(mTag1, mId1, n1);
+ }
+
+ private Notification.Action generateAction(int num) {
+ PendingIntent pi = PendingIntent.getActivity(mContext, num,
+ new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS),
+ PendingIntent.FLAG_IMMUTABLE);
+ return new Notification.Action.Builder(
+ Icon.createWithResource(mContext, R.drawable.ic_android),
+ mContext.getString(R.string.action, num), pi)
+ .build();
+ }
+
+ @Override
+ boolean autoStart() {
+ return true;
+ }
+
+ @Override
+ protected void test() {
+ status = WAIT_FOR_USER;
+ next();
+ }
+ }
+
+ /**
+ * Sends the user to settings filter out silent notifications for this notification listener.
+ * Sends silent and not silent notifs and makes sure only the non silent is received
+ */
+ private class NotificationTypeFilterTest extends InteractiveTestCase {
+ int mRetries = 3;
+ @Override
+ protected View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.nls_filter_test);
+
+ }
+
+ @Override
+ protected void setUp() {
+ createChannels();
+ sendNotifications();
+ sendNoisyNotification();
+ status = READY;
+ }
+
+ @Override
+ protected void tearDown() {
+ mNm.cancelAll();
+ MockListener.getInstance().resetData();
+ deleteChannels();
+ }
+
+ @Override
+ protected void test() {
+ if (MockListener.getInstance().getPosted(mTag4) == null) {
+ Log.d(TAG, "Could not find " + mTag4);
+ if (--mRetries > 0) {
+ sleep(100);
+ status = RETEST;
+ } else {
+ status = FAIL;
+ }
+ } else if (MockListener.getInstance().getPosted(mTag2) != null) {
+ logFail("Found" + mTag2);
+ status = FAIL;
+ } else {
+ status = PASS;
+ }
+ }
+ }
+
protected class SendUserToChangeFilter extends InteractiveTestCase {
@Override
protected View inflate(ViewGroup parent) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java
index 542fb36..f3e2680 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java
@@ -15,10 +15,6 @@
*/
package com.android.cts.verifier.p2p;
-import java.util.Collection;
-import java.util.Timer;
-import java.util.TimerTask;
-
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -40,6 +36,10 @@
import com.android.cts.verifier.p2p.testcase.TestCase;
import com.android.cts.verifier.p2p.testcase.TestCase.TestCaseListener;
+import java.util.Collection;
+import java.util.Timer;
+import java.util.TimerTask;
+
/**
* A base class for requester test activity.
*
@@ -135,8 +135,8 @@
}
@Override
- protected void onResume() {
- super.onResume();
+ protected void onStart() {
+ super.onStart();
/*
* If the target device is NOT set, search targets and show
* the target device list on the dialog.
@@ -152,8 +152,8 @@
}
@Override
- protected void onPause() {
- super.onPause();
+ protected void onStop() {
+ super.onStop();
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/ResponderTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/ResponderTestActivity.java
index 39f0bf8..405d4be 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/ResponderTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/ResponderTestActivity.java
@@ -101,8 +101,8 @@
}
@Override
- protected void onResume() {
- super.onResume();
+ protected void onStart() {
+ super.onStart();
mTestCase.start(this);
registerReceiver(mReceiver, mIntentFilter);
mP2pMgr.requestDeviceInfo(mChannel, wifiP2pDevice -> {
@@ -119,8 +119,8 @@
}
@Override
- protected void onPause() {
- super.onPause();
+ protected void onStop() {
+ super.onStop();
mTestCase.stop();
unregisterReceiver(mReceiver);
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/InteractiveVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/InteractiveVerifierActivity.java
index 5de1d8d..4ce47ef 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/InteractiveVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/InteractiveVerifierActivity.java
@@ -40,9 +40,6 @@
private static final String TAG = "InteractiveVerifier";
private static final String STATE = "state";
private static final String STATUS = "status";
- protected static final String TILE_PATH = "com.android.cts.verifier/" +
- "com.android.cts.verifier.qstiles.MockTileService";
- protected static final ComponentName TILE_NAME = ComponentName.unflattenFromString(TILE_PATH);
protected static final int SETUP = 0;
protected static final int READY = 1;
protected static final int RETEST = 2;
@@ -66,10 +63,13 @@
protected boolean setTileState(boolean enabled) {
int state = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
- mPackageManager.setComponentEnabledSetting(TILE_NAME, state, PackageManager.DONT_KILL_APP);
- return mPackageManager.getComponentEnabledSetting(TILE_NAME) == state;
+ mPackageManager.setComponentEnabledSetting(
+ getTileComponentName(), state, PackageManager.DONT_KILL_APP);
+ return mPackageManager.getComponentEnabledSetting(getTileComponentName()) == state;
}
+ protected abstract ComponentName getTileComponentName();
+
protected abstract class InteractiveTestCase {
protected boolean mUserVerified;
protected int status;
@@ -82,6 +82,8 @@
if (view == null) {
view = inflate(parent);
}
+ View requestAction = view.requireViewById(R.id.tiles_action_request);
+ requestAction.setVisibility(showRequestAction() ? View.VISIBLE : View.GONE);
return view;
}
@@ -131,6 +133,14 @@
((message == null) ? "" : ": " + message), e);
}
+ protected boolean showRequestAction() {
+ return false;
+ }
+
+ protected void requestAction() {
+
+ }
+
}
protected abstract int getTitleResource();
@@ -205,8 +215,6 @@
}
View item = test.view;
ImageView status = (ImageView) item.findViewById(R.id.tiles_status);
- View buttonPass = item.findViewById(R.id.tiles_action_pass);
- View buttonFail = item.findViewById(R.id.tiles_action_fail);
switch (test.status) {
case WAIT_FOR_USER:
status.setImageResource(R.drawable.fs_warning);
@@ -216,32 +224,35 @@
case READY:
case RETEST:
status.setImageResource(R.drawable.fs_clock);
- buttonPass.setEnabled(true);
- buttonPass.setClickable(true);
- buttonFail.setEnabled(true);
- buttonFail.setClickable(true);
+ setButtonsState(item, true);
break;
case FAIL:
status.setImageResource(R.drawable.fs_error);
- buttonFail.setClickable(false);
- buttonFail.setEnabled(false);
- buttonPass.setClickable(false);
- buttonPass.setEnabled(false);
+ setButtonsState(item, false);
break;
case PASS:
status.setImageResource(R.drawable.fs_good);
- buttonFail.setClickable(false);
- buttonFail.setEnabled(false);
- buttonPass.setClickable(false);
- buttonPass.setEnabled(false);
+ setButtonsState(item, false);
break;
}
status.invalidate();
}
+ private void setButtonsState(View parent, boolean enabledAndClickable) {
+ View buttonPass = parent.findViewById(R.id.tiles_action_pass);
+ View buttonFail = parent.findViewById(R.id.tiles_action_fail);
+ View buttonRequest = parent.findViewById(R.id.tiles_action_request);
+
+ buttonPass.setEnabled(enabledAndClickable);
+ buttonPass.setClickable(enabledAndClickable);
+ buttonFail.setEnabled(enabledAndClickable);
+ buttonFail.setClickable(enabledAndClickable);
+ buttonRequest.setEnabled(enabledAndClickable);
+ buttonRequest.setClickable(enabledAndClickable);
+ }
protected View createUserPassFail(ViewGroup parent, int messageId,
Object... messageFormatArgs) {
@@ -379,6 +390,12 @@
mCurrentTest.mUserVerified = true;
next();
break;
+ case R.id.tiles_action_request:
+ mCurrentTest.status = WAIT_FOR_USER;
+ v.setEnabled(false);
+ mHandler.post(mCurrentTest::requestAction);
+ next();
+ break;
default:
break;
}
@@ -393,4 +410,10 @@
Log.e(TAG, message, stackTrace);
}
+ protected void setPassFailButtonsEnabledState(boolean enabled) {
+ View currentView = mCurrentTest.view;
+ currentView.requireViewById(R.id.tiles_action_pass).setEnabled(enabled);
+ currentView.requireViewById(R.id.tiles_action_fail).setEnabled(enabled);
+ }
+
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/OWNERS
index cbc5f31..db047da 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/OWNERS
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/OWNERS
@@ -1,3 +1,4 @@
# Bug component: 78930
juliacr@google.com
-kozynski@google.com
\ No newline at end of file
+kozynski@google.com
+evanlaird@google.com
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/TileServiceRequestVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/TileServiceRequestVerifierActivity.java
new file mode 100644
index 0000000..ddaab1b
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/TileServiceRequestVerifierActivity.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.qstiles;
+
+import android.app.PendingIntent;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInstaller;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.cts.verifier.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+public class TileServiceRequestVerifierActivity extends InteractiveVerifierActivity {
+
+ private static final String TAG = "TileServiceRequestVerifierActivity";
+
+ private static final String ACTION_REMOVE_PACKAGE =
+ "com.android.cts.verifier.qstiles.ACTION_REMOVE_PACKAGE";
+
+ private CharSequence mTileLabel;
+ private static int sNextResultCode = 1000;
+ private static final String HELPER_PACKAGE_NAME = "com.android.cts.tileserviceapp";
+ private static final String HELPER_ACTIVITY_NAME = ".TileRequestActivity";
+ private static final String HELPER_TILE_NAME = ".RequestTileService";
+ private static final ComponentName HELPER_ACTIVITY_COMPONENT =
+ ComponentName.createRelative(HELPER_PACKAGE_NAME, HELPER_ACTIVITY_NAME);
+ private static final Intent INTENT = new Intent().setComponent(HELPER_ACTIVITY_COMPONENT);
+
+ // Keep track of activity started codes to handle results.
+ private final Map<Integer, Consumer<Integer>> mResultRegistry = new HashMap<>();
+
+ @Override
+ protected boolean setTileState(boolean enabled) {
+ // This tile is always enabled as long as the package is installed.
+ return true;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedState) {
+ mTileLabel = getString(R.string.tile_request_service_name);
+ super.onCreate(savedState);
+ }
+
+ private void registerForResult(Consumer<Integer> consumer) {
+ int code = sNextResultCode++;
+ mResultRegistry.put(code, consumer);
+ startActivityForResult(INTENT, code);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ mResultRegistry.get(requestCode).accept(resultCode);
+ }
+
+ private boolean isHelperAppInstalled() {
+ return getPackageManager().resolveActivity(INTENT, 0) != null;
+ }
+
+ @Override
+ protected ComponentName getTileComponentName() {
+ return ComponentName.createRelative(HELPER_PACKAGE_NAME, HELPER_TILE_NAME);
+ }
+
+ @Override
+ protected int getTitleResource() {
+ return R.string.tiles_request_test;
+ }
+
+ @Override
+ protected int getInstructionsResource() {
+ return R.string.tiles_request_info;
+ }
+
+ @Override
+ protected List<InteractiveTestCase> createTestItems() {
+ ArrayList<InteractiveTestCase> list = new ArrayList<>();
+ list.add(new UninstallPackage());
+ list.add(new InstallPackage());
+ list.add(new InstallPackageVerify());
+ list.add(new TileNotPresent());
+ list.add(new RequestAddTileDismiss());
+ list.add(new RequestAddTileAnswerNo());
+ list.add(new RequestAddTileCorrectInfo());
+ list.add(new RequestAddTileAnswerYes());
+ list.add(new TilePresentAfterRequest());
+ list.add(new RequestAddTileAlreadyAdded());
+ list.add(new UninstallPackage());
+ return list;
+ }
+
+ // Tests
+ private class UninstallPackage extends InteractiveTestCase {
+ @Override
+ protected View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.tiles_request_uninstall);
+ }
+
+ @Override
+ boolean autoStart() {
+ return true;
+ }
+
+ @Override
+ protected boolean showRequestAction() {
+ return true;
+ }
+
+ private BroadcastReceiver registerReceiver() {
+ BroadcastReceiver br = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int result = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_SUCCESS);
+ if (result == PackageInstaller.STATUS_PENDING_USER_ACTION) {
+ startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT));
+ return;
+ }
+ context.unregisterReceiver(this);
+ if (!isHelperAppInstalled()) {
+ status = PASS;
+ } else {
+ setFailed("Helper App still installed");
+ }
+ next();
+ }
+ };
+ mContext.registerReceiver(br, new IntentFilter(ACTION_REMOVE_PACKAGE));
+ return br;
+ }
+
+ @Override
+ protected void requestAction() {
+ PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
+ Log.i(TAG,
+ "Uninstalling package " + HELPER_PACKAGE_NAME + " using " + packageInstaller);
+ BroadcastReceiver br = registerReceiver();
+ try {
+ PendingIntent pi = PendingIntent.getBroadcast(mContext, /* requestCode */ 0,
+ new Intent(ACTION_REMOVE_PACKAGE), PendingIntent.FLAG_MUTABLE);
+ packageInstaller.uninstall(HELPER_ACTIVITY_COMPONENT.getPackageName(),
+ pi.getIntentSender());
+ status = WAIT_FOR_USER;
+ } catch (IllegalArgumentException e) {
+ mContext.unregisterReceiver(br);
+ status = PASS;
+ }
+ }
+
+ @Override
+ protected void test() {
+ if (status == READY) {
+ if (!isHelperAppInstalled()) {
+ status = PASS;
+ } else {
+ status = WAIT_FOR_USER;
+ }
+ next();
+ }
+ }
+ }
+
+ private class InstallPackage extends InteractiveTestCase {
+ @Override
+ protected View inflate(ViewGroup parent) {
+ return createUserPassFail(parent, R.string.tiles_request_install);
+ }
+
+ @Override
+ boolean autoStart() {
+ return true;
+ }
+
+ @Override
+ protected void test() {
+ status = WAIT_FOR_USER;
+ next();
+ }
+ }
+
+ private class InstallPackageVerify extends InteractiveTestCase {
+ @Override
+ protected View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.tiles_request_install_verify);
+ }
+
+ @Override
+ boolean autoStart() {
+ return true;
+ }
+
+ @Override
+ protected void test() {
+ if (isHelperAppInstalled()) {
+ status = PASS;
+ } else {
+ setFailed("Helper app not properly installed");
+ }
+ next();
+ }
+ }
+
+ private class TileNotPresent extends InteractiveTestCase {
+ @Override
+ protected View inflate(ViewGroup parent) {
+ return createUserPassFail(parent, R.string.tiles_request_tile_not_present, mTileLabel);
+ }
+
+ @Override
+ protected void test() {
+ status = WAIT_FOR_USER;
+ next();
+ }
+ }
+
+ private class RequestAddTileDismiss extends InteractiveTestCase {
+ @Override
+ protected View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.tiles_request_dismissed);
+ }
+
+ @Override
+ protected boolean showRequestAction() {
+ return true;
+ }
+
+ @Override
+ protected void requestAction() {
+ registerForResult(
+ integer -> {
+ if (integer.equals(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED)) {
+ status = PASS;
+ } else {
+ setFailed("Request called back with result: " + integer);
+ }
+ next();
+ }
+ );
+ }
+
+ @Override
+ protected void test() {
+ status = WAIT_FOR_USER;
+ next();
+ }
+ }
+
+ private class RequestAddTileAnswerNo extends InteractiveTestCase {
+ @Override
+ protected View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.tiles_request_answer_no);
+ }
+
+ @Override
+ protected boolean showRequestAction() {
+ return true;
+ }
+
+ @Override
+ protected void requestAction() {
+ registerForResult(
+ integer -> {
+ if (integer.equals(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED)) {
+ status = PASS;
+ } else {
+ setFailed("Request called back with result: " + integer);
+ }
+ next();
+ }
+ );
+ }
+
+ @Override
+ protected void test() {
+ status = WAIT_FOR_USER;
+ next();
+ }
+ }
+
+ private class RequestAddTileCorrectInfo extends InteractiveTestCase {
+
+ @Override
+ protected View inflate(ViewGroup parent) {
+ return createUserPassFail(parent, R.string.tiles_request_correct_info,
+ mContext.getString(R.string.tile_request_helper_app_name),
+ mTileLabel);
+ }
+
+ @Override
+ protected boolean showRequestAction() {
+ return true;
+ }
+
+ @Override
+ protected void requestAction() {
+ registerForResult(
+ integer -> {
+ if (integer.equals(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED)) {
+ status = WAIT_FOR_USER;
+ setPassFailButtonsEnabledState(true);
+ } else {
+ setFailed("Request called back with result: " + integer);
+ }
+ next();
+ }
+ );
+ }
+
+ @Override
+ protected void test() {
+ status = WAIT_FOR_USER;
+ setPassFailButtonsEnabledState(false);
+ next();
+ }
+ }
+
+ private class RequestAddTileAnswerYes extends InteractiveTestCase {
+ @Override
+ protected View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.tiles_request_answer_yes);
+ }
+
+ @Override
+ protected boolean showRequestAction() {
+ return true;
+ }
+
+ @Override
+ protected void requestAction() {
+ registerForResult(
+ integer -> {
+ if (integer.equals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED)) {
+ status = PASS;
+ } else {
+ setFailed("Request called back with result: " + integer);
+ }
+ next();
+ }
+ );
+ }
+
+ @Override
+ protected void test() {
+ status = WAIT_FOR_USER;
+ next();
+ }
+ }
+
+ private class TilePresentAfterRequest extends InteractiveTestCase {
+ @Override
+ protected View inflate(ViewGroup parent) {
+ return createUserPassFail(parent, R.string.tiles_request_tile_present, mTileLabel);
+ }
+
+ @Override
+ protected void test() {
+ status = WAIT_FOR_USER;
+ next();
+ }
+ }
+
+ private class RequestAddTileAlreadyAdded extends InteractiveTestCase {
+ @Override
+ protected View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.tiles_request_check_tile_already_added);
+ }
+
+ @Override
+ protected void test() {
+ registerForResult(
+ integer -> {
+ if (integer.equals(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED)) {
+ status = PASS;
+ } else {
+ setFailed("Request called back with result: " + integer);
+ }
+ next();
+ }
+ );
+ status = READY_AFTER_LONG_DELAY;
+ next();
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/TileServiceVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/TileServiceVerifierActivity.java
index a65156c..3071b65 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/TileServiceVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/TileServiceVerifierActivity.java
@@ -16,6 +16,7 @@
package com.android.cts.verifier.qstiles;
+import android.content.ComponentName;
import android.view.View;
import android.view.ViewGroup;
@@ -37,6 +38,13 @@
}
@Override
+ protected ComponentName getTileComponentName() {
+ String tilePath = "com.android.cts.verifier/"
+ + "com.android.cts.verifier.qstiles.MockTileService";
+ return ComponentName.unflattenFromString(tilePath);
+ }
+
+ @Override
protected List<InteractiveTestCase> createTestItems() {
ArrayList<InteractiveTestCase> list = new ArrayList<>();
list.add(new SettingUpTile());
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java
index 9bad7af..cb18251 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java
@@ -1,18 +1,6 @@
package com.android.cts.verifier.sensors;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import com.android.cts.verifier.R;
-import com.android.cts.verifier.sensors.base.SensorCtsVerifierTestActivity;
-import com.android.cts.verifier.sensors.helpers.SensorTestScreenManipulator;
-
import android.app.AlarmManager;
-import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -23,33 +11,26 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
-import android.hardware.TriggerEvent;
-import android.hardware.TriggerEventListener;
-import android.hardware.cts.helpers.MovementDetectorHelper;
-import android.hardware.cts.helpers.SensorStats;
-import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.SensorNotSupportedException;
import android.hardware.cts.helpers.SensorTestStateNotSupportedException;
import android.hardware.cts.helpers.TestSensorEnvironment;
-import android.hardware.cts.helpers.TestSensorEvent;
-import android.hardware.cts.helpers.TestSensorEventListener;
-import android.hardware.cts.helpers.TestSensorManager;
import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
-import android.hardware.cts.helpers.SensorNotSupportedException;
import android.hardware.cts.helpers.sensorverification.BatchArrivalVerification;
import android.hardware.cts.helpers.sensorverification.TimestampClockSourceVerification;
import android.os.IBinder;
import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import junit.framework.Assert;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.sensors.base.SensorCtsVerifierTestActivity;
+import com.android.cts.verifier.sensors.helpers.SensorTestScreenManipulator;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
public class DeviceSuspendTestActivity
extends SensorCtsVerifierTestActivity {
@@ -474,17 +455,10 @@
public String runAPWakeUpByAlarmNonWakeSensor(Sensor sensor, int maxReportLatencyUs)
- throws Throwable {
+ throws Throwable {
verifyBatchingSupport(sensor);
- int samplingPeriodUs = sensor.getMaxDelay();
- if (samplingPeriodUs == 0 || samplingPeriodUs > 200000) {
- // If maxDelay is not defined, set the value for 5 Hz.
- samplingPeriodUs = 200000;
- }
-
- long fifoBasedReportLatencyUs = maxBatchingPeriod(sensor, samplingPeriodUs);
- verifyBatchingPeriod(fifoBasedReportLatencyUs);
+ int samplingPeriodUs = sensor.getMinDelay();
TestSensorEnvironment environment = new TestSensorEnvironment(
this,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
index 5b19e7b..9451c60 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
@@ -68,7 +68,7 @@
private static final int CAPABILITIES_CHANGED_FOR_METERED_TIMEOUT_MS = 80_000;
private final Object mLock = new Object();
- private final ScheduledExecutorService mExecutorService;
+ private static ScheduledExecutorService sExecutorService;
private final WifiNetworkSuggestion.Builder mNetworkSuggestionBuilder =
new WifiNetworkSuggestion.Builder();
@@ -101,7 +101,6 @@
boolean setRequiresAppInteraction, boolean simulateConnectionFailure,
boolean setMeteredPostConnection) {
super(context);
- mExecutorService = Executors.newSingleThreadScheduledExecutor();
mSetBssid = setBssid;
mSetRequiresAppInteraction = setRequiresAppInteraction;
mSimulateConnectionFailure = simulateConnectionFailure;
@@ -299,7 +298,7 @@
// Step: Trigger scans periodically to trigger network selection quicker.
if (DBG) Log.v(TAG, "Triggering scan periodically");
- mExecutorService.scheduleAtFixedRate(() -> {
+ sExecutorService.scheduleAtFixedRate(() -> {
if (!mWifiManager.startScan()) {
Log.w(TAG, "Failed to trigger scan");
}
@@ -459,12 +458,13 @@
@Override
protected void setUp() {
super.setUp();
+ sExecutorService = Executors.newSingleThreadScheduledExecutor();
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
}
@Override
protected void tearDown() {
- mExecutorService.shutdownNow();
+ sExecutorService.shutdownNow();
if (mBroadcastReceiver != null) {
mContext.unregisterReceiver(mBroadcastReceiver);
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathForceChannelSetupPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathForceChannelSetupPublishTestActivity.java
new file mode 100644
index 0000000..2d53ad1
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathForceChannelSetupPublishTestActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wifiaware;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, in-band and force channel setup on publish
+ */
+public class DataPathForceChannelSetupPublishTestActivity extends BaseTestActivity {
+ @Override
+ protected BaseTestCase getTestCase(Context context) {
+ return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+ /* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ true,
+ /* acceptAny */ false, /* forceChannel */ true);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setInfoResources(R.string.aware_data_path_force_channel_setup_publish,
+ R.string.aware_data_path_force_channel_setup_publish_info, 0);
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathForceChannelSetupSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathForceChannelSetupSubscribeTestActivity.java
new file mode 100644
index 0000000..502abc8
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathForceChannelSetupSubscribeTestActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wifiaware;
+
+import android.content.Context;
+
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, in-band and force channel setup on Subscribe
+ */
+public class DataPathForceChannelSetupSubscribeTestActivity extends BaseTestActivity {
+ @Override
+ protected BaseTestCase getTestCase(Context context) {
+ return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+ /* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ true,
+ /* acceptAny */ false, /* forceChannel */ true);
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeAcceptAnyTestActivity.java
index e81528e..682bfcf 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeAcceptAnyTestActivity.java
@@ -27,6 +27,7 @@
@Override
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ false,
- /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ false);
+ /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ false,
+ /* forceChannel */false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java
index 888c4cb..15f9d35 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java
@@ -27,6 +27,7 @@
@Override
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ false,
- /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ false);
+ /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ false,
+ /* forceChannel */false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeAcceptAnyTestActivity.java
index b25c3ef..21a2cb3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeAcceptAnyTestActivity.java
@@ -27,6 +27,7 @@
@Override
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ false,
- /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ false);
+ /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ false,
+ /* forceChannel */false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java
index 9bfd9d1..5f5145e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java
@@ -27,6 +27,7 @@
@Override
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ false,
- /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ false);
+ /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ false,
+ /* forceChannel */false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishAcceptAnyTestActivity.java
index 0888e5e..b07d3a0 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishAcceptAnyTestActivity.java
@@ -29,7 +29,8 @@
@Override
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ true,
- /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ true);
+ /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ true,
+ /* forceChannel */false);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java
index 2bd5313..e80bf67 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java
@@ -29,7 +29,8 @@
@Override
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ true,
- /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ false);
+ /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ false,
+ /* forceChannel */false);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishAcceptAnyTestActivity.java
index ed9e17a..25d7153 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishAcceptAnyTestActivity.java
@@ -29,7 +29,8 @@
@Override
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ true,
- /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ true);
+ /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ true,
+ /* forceChannel */false);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java
index 6d7e8ca..06ac548 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java
@@ -29,7 +29,8 @@
@Override
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ true,
- /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ false);
+ /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ false,
+ /* forceChannel */false);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeAcceptAnyTestActivity.java
index 62275ec..b166faf 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeAcceptAnyTestActivity.java
@@ -28,6 +28,6 @@
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
/* isPublish */ false, /* isUnsolicited */ false, /* usePmk */ false,
- /* acceptAny */ false);
+ /* acceptAny */ false, /* forceChannel */false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java
index e5f77e9..86a86b9 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java
@@ -28,6 +28,6 @@
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
/* isPublish */ false, /* isUnsolicited */ false, /* usePmk */ false,
- /* acceptAny */ false);
+ /* acceptAny */ false, /* forceChannel */false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeAcceptAnyTestActivity.java
index 88ea2bb..24df500 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeAcceptAnyTestActivity.java
@@ -28,6 +28,6 @@
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
/* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ false,
- /* acceptAny */ false);
+ /* acceptAny */ false, /* forceChannel */false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java
index 46413c3..f0f7b44 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java
@@ -28,6 +28,6 @@
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
/* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ false,
- /* acceptAny */ false);
+ /* acceptAny */ false, /* forceChannel */false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishAcceptAnyTestActivity.java
index 2a24d85..188cfbd 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishAcceptAnyTestActivity.java
@@ -30,7 +30,7 @@
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
/* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ false,
- /* acceptAny */ true);
+ /* acceptAny */ true, /* forceChannel */false);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java
index 9c06b5d..114862d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java
@@ -30,7 +30,7 @@
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
/* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ false,
- /* acceptAny */ false);
+ /* acceptAny */ false, /* forceChannel */false);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishAcceptAnyTestActivity.java
index 2616357..5ba364a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishAcceptAnyTestActivity.java
@@ -30,7 +30,7 @@
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
/* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ false,
- /* acceptAny */ true);
+ /* acceptAny */ true, /* forceChannel */false);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java
index 601f75b..1f5666f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java
@@ -30,7 +30,7 @@
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
/* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ false,
- /* acceptAny */ false);
+ /* acceptAny */ false, /* forceChannel */false);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeAcceptAnyTestActivity.java
index 136b296..0c8c494 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeAcceptAnyTestActivity.java
@@ -28,6 +28,6 @@
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
/* isPublish */ false, /* isUnsolicited */ false, /* usePmk */ true,
- /* acceptAny */ false);
+ /* acceptAny */ false, /* forceChannel */false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java
index 11c1e64..1fc76fc 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java
@@ -28,6 +28,6 @@
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
/* isPublish */ false, /* isUnsolicited */ false, /* usePmk */ true,
- /* acceptAny */ false);
+ /* acceptAny */ false, /* forceChannel */false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeAcceptAnyTestActivity.java
index 44cab45..b158720 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeAcceptAnyTestActivity.java
@@ -28,6 +28,6 @@
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
/* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ true,
- /* acceptAny */ false);
+ /* acceptAny */ false, /* forceChannel */false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java
index 28f6c76..8cabd8f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java
@@ -28,6 +28,6 @@
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
/* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ true,
- /* acceptAny */ false);
+ /* acceptAny */ false, /* forceChannel */false);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishAcceptAnyTestActivity.java
index 69cdfd2..fc23c42 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishAcceptAnyTestActivity.java
@@ -30,7 +30,7 @@
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
/* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ true,
- /* acceptAny */ true);
+ /* acceptAny */ true, /* forceChannel */false);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java
index e0c0f29..d01fdbb 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java
@@ -30,7 +30,7 @@
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
/* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ true,
- /* acceptAny */ false);
+ /* acceptAny */ false, /* forceChannel */false);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishAcceptAnyTestActivity.java
index 4c557e9..e4fc753 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishAcceptAnyTestActivity.java
@@ -30,7 +30,7 @@
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
/* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ true,
- /* acceptAny */ true);
+ /* acceptAny */ true, /* forceChannel */false);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java
index a9154ec..68267eb 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java
@@ -30,7 +30,7 @@
protected BaseTestCase getTestCase(Context context) {
return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
/* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ true,
- /* acceptAny */ false);
+ /* acceptAny */ false, /* forceChannel */false);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java
index b1b62bf..1e81992 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java
@@ -232,6 +232,21 @@
R.string.aware_subscribe,
DataPathPmkActiveSubscribeAcceptAnyTestActivity.class.getName(),
new Intent(this, DataPathPmkActiveSubscribeAcceptAnyTestActivity.class), null));
+
+ if (mWifiAwareManager.isSetChannelOnDataPathSupported()) {
+ adapter.add(TestListAdapter.TestListItem.newCategory(this,
+ R.string.aware_dp_ib_force_channel_setup));
+ adapter.add(TestListAdapter.TestListItem.newTest(this,
+ R.string.aware_publish,
+ DataPathForceChannelSetupPublishTestActivity.class.getName(),
+ new Intent(this, DataPathForceChannelSetupPublishTestActivity.class),
+ null));
+ adapter.add(TestListAdapter.TestListItem.newTest(this,
+ R.string.aware_subscribe,
+ DataPathForceChannelSetupSubscribeTestActivity.class.getName(),
+ new Intent(this, DataPathForceChannelSetupSubscribeTestActivity.class),
+ null));
+ }
}
adapter.registerDataSetObserver(new DataSetObserver() {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java
index 737234a..af2441c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java
@@ -24,6 +24,7 @@
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.wifi.aware.PublishDiscoverySession;
+import android.net.wifi.aware.WifiAwareChannelInfo;
import android.net.wifi.aware.WifiAwareNetworkInfo;
import android.net.wifi.aware.WifiAwareNetworkSpecifier;
import android.util.Log;
@@ -84,10 +85,13 @@
private static final byte[] MSG_CLIENT_TO_SERVER = "GET SOME BYTES".getBytes();
private static final byte[] MSG_SERVER_TO_CLIENT = "PUT SOME OTHER BYTES".getBytes();
+ private static final int CHANNEL_IN_MHZ = 2412;
+
private boolean mIsSecurityOpen;
private boolean mUsePmk;
private boolean mIsPublish;
private boolean mIsAcceptAny;
+ private boolean mForceChannel;
private Thread mClientServerThread;
private ConnectivityManager mCm;
private CallbackUtils.NetworkCb mNetworkCb;
@@ -95,13 +99,14 @@
private static int sSDKLevel = android.os.Build.VERSION.SDK_INT;
public DataPathInBandTestCase(Context context, boolean isSecurityOpen, boolean isPublish,
- boolean isUnsolicited, boolean usePmk, boolean acceptAny) {
+ boolean isUnsolicited, boolean usePmk, boolean acceptAny, boolean forceChannel) {
super(context, isUnsolicited, false);
mIsSecurityOpen = isSecurityOpen;
mUsePmk = usePmk;
mIsPublish = isPublish;
mIsAcceptAny = acceptAny;
+ mForceChannel = forceChannel;
}
@Override
@@ -182,6 +187,15 @@
nsBuilder.setPskPassphrase(PASSPHRASE);
}
}
+ if (mForceChannel) {
+ nsBuilder.setChannelInMhz(CHANNEL_IN_MHZ, true);
+ WifiAwareNetworkSpecifier ns = nsBuilder.build();
+ if (ns.getChannelInMhz() != CHANNEL_IN_MHZ || !ns.isChannelRequired()) {
+ Log.e(TAG, "executeTestSubscriber: channel configure for data-path is not match");
+ return false;
+ }
+ }
+
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
nsBuilder.build()).build();
@@ -214,14 +228,40 @@
mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_network_success));
if (DBG) Log.d(TAG, "executeTestSubscriber: network request granted - AVAILABLE");
- if (!mIsSecurityOpen) {
- if (!(info.second.getTransportInfo() instanceof WifiAwareNetworkInfo)) {
+ if (!(info.second.getTransportInfo() instanceof WifiAwareNetworkInfo)) {
+ setFailureReason(mContext.getString(R.string.aware_status_network_failed));
+ Log.e(TAG, "executeTestSubscriber: did not get WifiAwareNetworkInfo from peer!?");
+ return false;
+ }
+ WifiAwareNetworkInfo peerAwareInfo =
+ (WifiAwareNetworkInfo) info.second.getTransportInfo();
+ StringBuilder builder = new StringBuilder();
+ for (WifiAwareChannelInfo channelInfo : peerAwareInfo.getChannelInfos()) {
+ builder.append(channelInfo.toString());
+ }
+ if (DBG) Log.d(TAG, "executeTestSubscriber: channelInfo:" + builder.toString());
+
+ if (mForceChannel) {
+ if (peerAwareInfo.getChannelInfos().size() != 1) {
setFailureReason(mContext.getString(R.string.aware_status_network_failed));
- Log.e(TAG, "executeTestSubscriber: did not get WifiAwareNetworkInfo from peer!?");
+ Log.e(TAG, "executeTestSubscriber: number of channel info is incorrect");
return false;
}
- WifiAwareNetworkInfo peerAwareInfo =
- (WifiAwareNetworkInfo) info.second.getTransportInfo();
+ WifiAwareChannelInfo channelInfo = peerAwareInfo.getChannelInfos().get(0);
+ if (channelInfo.getChannelFreqInMhz() != CHANNEL_IN_MHZ) {
+ setFailureReason(mContext.getString(R.string.aware_status_network_failed));
+ Log.e(TAG, "executeTestSubscriber: channel freq is not match the request");
+ return false;
+ }
+ if (DBG) {
+ Log.d(TAG, "executeTestSubscriber: ChannelFreqInMhz="
+ + channelInfo.getChannelFreqInMhz()
+ + " ChannelBandWidth=" + channelInfo.getChannelBandwidth()
+ + " NumSpatialStreams=" + channelInfo.getNumSpatialStreams());
+ }
+ }
+
+ if (!mIsSecurityOpen) {
Inet6Address peerIpv6 = peerAwareInfo.getPeerIpv6Addr();
int peerPort = peerAwareInfo.getPort();
int peerTransportProtocol = peerAwareInfo.getTransportProtocol();
@@ -385,6 +425,14 @@
}
nsBuilder.setPort(port).setTransportProtocol(6); // 6 == TCP
}
+ if (mForceChannel) {
+ nsBuilder.setChannelInMhz(CHANNEL_IN_MHZ, true);
+ WifiAwareNetworkSpecifier ns = nsBuilder.build();
+ if (ns.getChannelInMhz() != CHANNEL_IN_MHZ || !ns.isChannelRequired()) {
+ Log.e(TAG, "executeTestSubscriber: channel configure for data-path is not match");
+ return false;
+ }
+ }
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
nsBuilder.build()).build();
diff --git a/apps/TileServiceApp/Android.bp b/apps/TileServiceApp/Android.bp
new file mode 100644
index 0000000..55038f3
--- /dev/null
+++ b/apps/TileServiceApp/Android.bp
@@ -0,0 +1,24 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsTileServiceApp",
+ srcs: ["src/**/*.java"],
+ sdk_version: "current",
+ min_sdk_version: "31",
+}
diff --git a/apps/TileServiceApp/AndroidManifest.xml b/apps/TileServiceApp/AndroidManifest.xml
new file mode 100644
index 0000000..be693b1
--- /dev/null
+++ b/apps/TileServiceApp/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.tileserviceapp">
+
+ <uses-sdk android:minSdkVersion="31"/>
+
+ <application android:label="CtsTileServiceApp"
+ android:icon="@android:drawable/ic_dialog_alert">
+ <activity android:name=".TileRequestActivity"
+ android:exported="true">
+ </activity>
+
+ <service android:name=".RequestTileService"
+ android:icon="@android:drawable/ic_dialog_alert"
+ android:label="@string/tile_request_service_name"
+ android:exported="true"
+ android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+ <intent-filter>
+ <action android:name="android.service.quicksettings.action.QS_TILE" />
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
diff --git a/apps/TileServiceApp/OWNERS b/apps/TileServiceApp/OWNERS
new file mode 100644
index 0000000..3ae17a9
--- /dev/null
+++ b/apps/TileServiceApp/OWNERS
@@ -0,0 +1 @@
+include /apps/CtsVerifier/src/com/android/cts/verifier/qstiles/OWNERS
\ No newline at end of file
diff --git a/apps/TileServiceApp/res/values/strings.xml b/apps/TileServiceApp/res/values/strings.xml
new file mode 100755
index 0000000..79ef54e
--- /dev/null
+++ b/apps/TileServiceApp/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="tile_request_service_name">Request Tile Service</string>
+</resources>
diff --git a/apps/TileServiceApp/src/com/android/cts/tileserviceapp/RequestTileService.java b/apps/TileServiceApp/src/com/android/cts/tileserviceapp/RequestTileService.java
new file mode 100644
index 0000000..6000c7b
--- /dev/null
+++ b/apps/TileServiceApp/src/com/android/cts/tileserviceapp/RequestTileService.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.tileserviceapp;
+
+import android.service.quicksettings.TileService;
+
+/** TileService for test */
+public class RequestTileService extends TileService {
+}
diff --git a/apps/TileServiceApp/src/com/android/cts/tileserviceapp/TileRequestActivity.java b/apps/TileServiceApp/src/com/android/cts/tileserviceapp/TileRequestActivity.java
new file mode 100644
index 0000000..08f7806
--- /dev/null
+++ b/apps/TileServiceApp/src/com/android/cts/tileserviceapp/TileRequestActivity.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.tileserviceapp;
+
+import android.app.Activity;
+import android.app.StatusBarManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * A simple activity that requests permissions and returns the result.
+ */
+public final class TileRequestActivity extends Activity {
+
+ private static final String TAG = "TileRequestActivity";
+ private String mTileLabel;
+ private StatusBarManager mStatusBarManager;
+ private Icon mIcon;
+
+ private ComponentName getTileComponentName() {
+ return new ComponentName(this, RequestTileService.class);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mTileLabel = getString(R.string.tile_request_service_name);
+ mStatusBarManager = getSystemService(StatusBarManager.class);
+ mIcon = Icon.createWithResource(this, android.R.drawable.ic_dialog_alert);
+
+ final Intent received = getIntent();
+ Log.d(TAG, "Started with " + received);
+ requestTile();
+ }
+
+ private void requestTile() {
+ mStatusBarManager.requestAddTileService(
+ getTileComponentName(),
+ mTileLabel,
+ mIcon,
+ getMainExecutor(),
+ integer -> {
+ setResult(integer);
+ finish();
+ }
+ );
+ }
+}
diff --git a/apps/VpnApp/Android.bp b/apps/VpnApp/Android.bp
index 898f4bd..a6ee6e4 100644
--- a/apps/VpnApp/Android.bp
+++ b/apps/VpnApp/Android.bp
@@ -28,6 +28,7 @@
defaults: ["CtsVpnAppDefaults"],
manifest: "api23/AndroidManifest.xml",
test_suites: [
+ "arcts",
"cts",
"general-tests",
],
@@ -38,6 +39,7 @@
defaults: ["CtsVpnAppDefaults"],
manifest: "api24/AndroidManifest.xml",
test_suites: [
+ "arcts",
"cts",
"general-tests",
],
@@ -48,6 +50,7 @@
defaults: ["CtsVpnAppDefaults"],
manifest: "latest/AndroidManifest.xml",
test_suites: [
+ "arcts",
"cts",
"general-tests",
],
@@ -58,6 +61,7 @@
defaults: ["CtsVpnAppDefaults"],
manifest: "notalwayson/AndroidManifest.xml",
test_suites: [
+ "arcts",
"cts",
"general-tests",
],
diff --git a/common/device-side/bedstead/deviceadminapp/src/main/AndroidManifest.xml b/common/device-side/bedstead/deviceadminapp/src/main/AndroidManifest.xml
index 35b0796..7733f55 100644
--- a/common/device-side/bedstead/deviceadminapp/src/main/AndroidManifest.xml
+++ b/common/device-side/bedstead/deviceadminapp/src/main/AndroidManifest.xml
@@ -19,7 +19,7 @@
package="com.android.bedstead.deviceadminapp"
android:targetSandboxVersion="2">
- <application android:testOnly="true">
+ <application>
<receiver android:name="com.android.eventlib.premade.EventLibDeviceAdminReceiver"
android:permission="android.permission.BIND_DEVICE_ADMIN"
android:exported="true">
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/IpcBroadcastReceiver.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/IpcBroadcastReceiver.java
index b2d51f8..3f8b590 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/IpcBroadcastReceiver.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/IpcBroadcastReceiver.java
@@ -32,6 +32,10 @@
<pre><code>
<receiver android:name="com.android.bedstead.dpmwrapper.IpcBroadcastReceiver"
android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.bedstead.dpmwrapper.action.WRAPPED_MANAGER_CALL"/>
+ </intent-filter>
+ </receiver>
</code></pre>
*/
// TODO(b/176993670): remove when DpmWrapper IPC mechanism changes
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java
index 0f33307..d96c0b1 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java
@@ -317,7 +317,8 @@
case RESULT_NOT_SENT_TO_ANY_RECEIVER:
fail("Didn't receive result from ordered broadcast - did you override "
+ receiverClassName + ".onReceive() to call "
- + "DeviceOwnerHelper.runManagerMethod()?");
+ + "DeviceOwnerHelper.runManagerMethod()? Did you add "
+ + ACTION_WRAPPED_MANAGER_CALL + " to its intent filter / manifest?");
return null;
default:
fail("Received invalid result for method %s: %s", methodName, result);
diff --git a/common/device-side/bedstead/harrier/Android.bp b/common/device-side/bedstead/harrier/Android.bp
index e3f6715..53c09da 100644
--- a/common/device-side/bedstead/harrier/Android.bp
+++ b/common/device-side/bedstead/harrier/Android.bp
@@ -16,6 +16,40 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+java_library_host {
+ name: "HarrierCommon",
+ srcs: [
+ "common/src/main/java/**/*.java",
+ ],
+
+ static_libs: [
+ "junit",
+ "auto_value_annotations",
+ "guava",
+ "NeneCommon"
+ ],
+
+ plugins: ["auto_annotation_plugin"]
+}
+
+android_library {
+ name: "HarrierCommonAndroid",
+ srcs: [
+ "common/src/main/java/**/*.java",
+ ],
+
+ static_libs: [
+ "junit",
+ "auto_value_annotations",
+ "guava",
+ "NeneCommonAndroid"
+ ],
+
+ manifest: "src/main/AndroidManifest.xml",
+ min_sdk_version: "27",
+ plugins: ["auto_annotation_plugin"]
+}
+
android_library {
name: "Harrier",
sdk_version: "test_current",
@@ -27,6 +61,7 @@
static_libs: [
"Nene",
"RemoteDPC",
+ "HarrierCommonAndroid",
"compatibility-device-util-axt",
"androidx.test.ext.junit",
"auto_value_annotations"
@@ -34,7 +69,6 @@
manifest: "src/main/AndroidManifest.xml",
min_sdk_version: "27",
- plugins: ["auto_annotation_plugin"],
}
android_test {
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/BedsteadFrameworkMethod.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/BedsteadFrameworkMethod.java
new file mode 100644
index 0000000..77bb8d1
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/BedsteadFrameworkMethod.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier;
+
+import com.google.common.base.Objects;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+/**
+ * {@link FrameworkMethod} subclass which allows modifying the test name and annotations.
+ */
+public final class BedsteadFrameworkMethod extends FrameworkMethod {
+
+ private final Annotation mParameterizedAnnotation;
+ private final Map<Class<? extends Annotation>, Annotation> mAnnotationsMap =
+ new HashMap<>();
+ private Annotation[] mAnnotations;
+
+ public BedsteadFrameworkMethod(Method method) {
+ this(method, /* parameterizedAnnotation= */ null);
+ }
+
+ public BedsteadFrameworkMethod(Method method,
+ @Nullable Annotation parameterizedAnnotation) {
+ super(method);
+ mParameterizedAnnotation = parameterizedAnnotation;
+
+ calculateAnnotations();
+ }
+
+ private void calculateAnnotations() {
+ List<Annotation> annotations =
+ new ArrayList<>(Arrays.asList(getDeclaringClass().getAnnotations()));
+ annotations.sort(BedsteadJUnit4::annotationSorter);
+
+ annotations.addAll(Arrays.stream(getMethod().getAnnotations())
+ .sorted(BedsteadJUnit4::annotationSorter)
+ .collect(Collectors.toList()));
+
+ BedsteadJUnit4.parseEnterpriseAnnotations(annotations);
+
+ BedsteadJUnit4.resolveRecursiveAnnotations(annotations, mParameterizedAnnotation);
+
+ mAnnotations = annotations.toArray(new Annotation[0]);
+ for (Annotation annotation : annotations) {
+ if (annotation instanceof DynamicParameterizedAnnotation) {
+ continue; // don't return this
+ }
+ mAnnotationsMap.put(annotation.annotationType(), annotation);
+ }
+ }
+
+ @Override
+ public String getName() {
+ if (mParameterizedAnnotation == null) {
+ return super.getName();
+ }
+ return super.getName() + "[" + BedsteadJUnit4.getParameterName(mParameterizedAnnotation)
+ + "]";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!super.equals(obj)) {
+ return false;
+ }
+
+ if (!(obj instanceof BedsteadFrameworkMethod)) {
+ return false;
+ }
+
+ BedsteadFrameworkMethod other = (BedsteadFrameworkMethod) obj;
+
+ return Objects.equal(mParameterizedAnnotation, other.mParameterizedAnnotation);
+ }
+
+ @Override
+ public Annotation[] getAnnotations() {
+ return mAnnotations;
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
+ return (T) mAnnotationsMap.get(annotationType);
+ }
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/BedsteadJUnit4.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/BedsteadJUnit4.java
new file mode 100644
index 0000000..646291a
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/BedsteadJUnit4.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier;
+
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+import com.android.bedstead.harrier.annotations.IntTestParameter;
+import com.android.bedstead.harrier.annotations.StringTestParameter;
+import com.android.bedstead.harrier.annotations.enterprise.CanSetPolicyTest;
+import com.android.bedstead.harrier.annotations.enterprise.CannotSetPolicyTest;
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+import com.android.bedstead.harrier.annotations.enterprise.NegativePolicyTest;
+import com.android.bedstead.harrier.annotations.enterprise.PositivePolicyTest;
+import com.android.bedstead.harrier.annotations.meta.ParameterizedAnnotation;
+import com.android.bedstead.harrier.annotations.meta.RepeatingAnnotation;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeNone;
+import com.android.bedstead.nene.annotations.Nullable;
+import com.android.bedstead.nene.exceptions.NeneException;
+
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.TestClass;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Parameter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * A JUnit test runner for use with Bedstead.
+ */
+public final class BedsteadJUnit4 extends BlockJUnit4ClassRunner {
+
+ private static final String BEDSTEAD_PACKAGE_NAME = "com.android.bedstead";
+
+ // These are annotations which are not included indirectly
+ private static final Set<String> sIgnoredAnnotationPackages = new HashSet<>();
+
+ static {
+ sIgnoredAnnotationPackages.add("java.lang.annotation");
+ sIgnoredAnnotationPackages.add("com.android.bedstead.harrier.annotations.meta");
+ sIgnoredAnnotationPackages.add("kotlin.*");
+ sIgnoredAnnotationPackages.add("org.junit");
+ }
+
+ static int annotationSorter(Annotation a, Annotation b) {
+ return getAnnotationWeight(a) - getAnnotationWeight(b);
+ }
+
+ private static int getAnnotationWeight(Annotation annotation) {
+ if (annotation instanceof DynamicParameterizedAnnotation) {
+ // Special case, not important
+ return AnnotationRunPrecedence.PRECEDENCE_NOT_IMPORTANT;
+ }
+
+ if (!annotation.annotationType().getPackage().getName().startsWith(BEDSTEAD_PACKAGE_NAME)) {
+ return AnnotationRunPrecedence.FIRST;
+ }
+
+ try {
+ return (int) annotation.annotationType().getMethod("weight").invoke(annotation);
+ } catch (NoSuchMethodException e) {
+ // Default to PRECEDENCE_NOT_IMPORTANT if no weight is found on the annotation.
+ return AnnotationRunPrecedence.PRECEDENCE_NOT_IMPORTANT;
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new NeneException("Failed to invoke weight on this annotation: " + annotation, e);
+ }
+ }
+
+ static String getParameterName(Annotation annotation) {
+ if (annotation instanceof DynamicParameterizedAnnotation) {
+ return ((DynamicParameterizedAnnotation) annotation).name();
+ }
+ return annotation.annotationType().getSimpleName();
+ }
+
+ /**
+ * Resolve annotations recursively.
+ *
+ * @param parameterizedAnnotation The class of the parameterized annotation to expand, if any
+ */
+ public static void resolveRecursiveAnnotations(List<Annotation> annotations,
+ @Nullable Annotation parameterizedAnnotation) {
+ int index = 0;
+ while (index < annotations.size()) {
+ Annotation annotation = annotations.get(index);
+ annotations.remove(index);
+ List<Annotation> replacementAnnotations =
+ getReplacementAnnotations(annotation, parameterizedAnnotation);
+ replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
+ annotations.addAll(index, replacementAnnotations);
+ index += replacementAnnotations.size();
+ }
+ }
+
+ private static boolean isParameterizedAnnotation(Annotation annotation) {
+ if (annotation instanceof DynamicParameterizedAnnotation) {
+ return true;
+ }
+
+ return annotation.annotationType().getAnnotation(ParameterizedAnnotation.class) != null;
+ }
+
+ private static Annotation[] getIndirectAnnotations(Annotation annotation) {
+ if (annotation instanceof DynamicParameterizedAnnotation) {
+ return ((DynamicParameterizedAnnotation) annotation).annotations();
+ }
+ return annotation.annotationType().getAnnotations();
+ }
+
+ private static boolean isRepeatingAnnotation(Annotation annotation) {
+ if (annotation instanceof DynamicParameterizedAnnotation) {
+ return false;
+ }
+
+ return annotation.annotationType().getAnnotation(RepeatingAnnotation.class) != null;
+ }
+
+ private static List<Annotation> getReplacementAnnotations(Annotation annotation,
+ @Nullable Annotation parameterizedAnnotation) {
+ List<Annotation> replacementAnnotations = new ArrayList<>();
+
+ if (isRepeatingAnnotation(annotation)) {
+ try {
+ Annotation[] annotations =
+ (Annotation[]) annotation.annotationType()
+ .getMethod("value").invoke(annotation);
+ Collections.addAll(replacementAnnotations, annotations);
+ return replacementAnnotations;
+ } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+ throw new NeneException("Error expanding repeated annotations", e);
+ }
+ }
+
+ if (isParameterizedAnnotation(annotation) && !annotation.equals(parameterizedAnnotation)) {
+ return replacementAnnotations;
+ }
+
+ for (Annotation indirectAnnotation : getIndirectAnnotations(annotation)) {
+ if (shouldSkipAnnotation(annotation)) {
+ continue;
+ }
+
+ replacementAnnotations.addAll(getReplacementAnnotations(
+ indirectAnnotation, parameterizedAnnotation));
+ }
+
+ if (!(annotation instanceof DynamicParameterizedAnnotation)) {
+ // We drop the fake annotation once it's replaced
+ replacementAnnotations.add(annotation);
+ }
+
+ return replacementAnnotations;
+ }
+
+ private static boolean shouldSkipAnnotation(Annotation annotation) {
+ if (annotation instanceof DynamicParameterizedAnnotation) {
+ return false;
+ }
+
+ String annotationPackage = annotation.annotationType().getPackage().getName();
+
+ for (String ignoredPackage : sIgnoredAnnotationPackages) {
+ if (ignoredPackage.endsWith(".*")) {
+ if (annotationPackage.startsWith(
+ ignoredPackage.substring(0, ignoredPackage.length() - 2))) {
+ return true;
+ }
+ } else if (annotationPackage.equals(ignoredPackage)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public BedsteadJUnit4(Class<?> testClass) throws InitializationError {
+ super(testClass);
+ }
+
+ private boolean annotationShouldBeSkipped(Annotation annotation) {
+ if (annotation instanceof DynamicParameterizedAnnotation) {
+ return false;
+ }
+
+ return annotation.annotationType().equals(IncludeNone.class);
+ }
+
+ @Override
+ protected List<FrameworkMethod> computeTestMethods() {
+ TestClass testClass = getTestClass();
+
+ List<FrameworkMethod> basicTests = testClass.getAnnotatedMethods(Test.class);
+ List<FrameworkMethod> modifiedTests = new ArrayList<>();
+
+ for (FrameworkMethod m : basicTests) {
+ Set<Annotation> parameterizedAnnotations = getParameterizedAnnotations(m);
+
+ if (parameterizedAnnotations.isEmpty()) {
+ // Unparameterized, just add the original
+ modifiedTests.add(new BedsteadFrameworkMethod(m.getMethod()));
+ }
+
+ for (Annotation annotation : parameterizedAnnotations) {
+ if (annotationShouldBeSkipped(annotation)) {
+ // Special case - does not generate a run
+ continue;
+ }
+ modifiedTests.add(
+ new BedsteadFrameworkMethod(m.getMethod(), annotation));
+ }
+ }
+
+ modifiedTests = generateGeneralParameterisationMethods(modifiedTests);
+
+ sortMethodsByBedsteadAnnotations(modifiedTests);
+
+ return modifiedTests;
+ }
+
+ private static List<FrameworkMethod> generateGeneralParameterisationMethods(
+ List<FrameworkMethod> modifiedTests) {
+ return modifiedTests.stream()
+ .flatMap(BedsteadJUnit4::generateGeneralParameterisationMethods)
+ .collect(Collectors.toList());
+ }
+
+ private static Stream<FrameworkMethod> generateGeneralParameterisationMethods(
+ FrameworkMethod method) {
+ Stream<FrameworkMethod> expandedMethods = Stream.of(method);
+ if (method.getMethod().getParameterCount() == 0) {
+ return expandedMethods;
+ }
+
+ for (Parameter parameter : method.getMethod().getParameters()) {
+ List<Annotation> annotations = new ArrayList<>(
+ Arrays.asList(parameter.getAnnotations()));
+ resolveRecursiveAnnotations(annotations, /* parameterizedAnnotation= */ null);
+
+ boolean hasParameterised = false;
+
+ for (Annotation annotation : annotations) {
+ if (annotation instanceof StringTestParameter) {
+ if (hasParameterised) {
+ throw new IllegalStateException(
+ "Each parameter can only have a single parameterised annotation");
+ }
+ hasParameterised = true;
+
+ StringTestParameter stringTestParameter = (StringTestParameter) annotation;
+
+ expandedMethods = expandedMethods.flatMap(
+ i -> applyStringTestParameter(i, stringTestParameter));
+ } else if (annotation instanceof IntTestParameter) {
+ if (hasParameterised) {
+ throw new IllegalStateException(
+ "Each parameter can only have a single parameterised annotation");
+ }
+ hasParameterised = true;
+
+ IntTestParameter intTestParameter = (IntTestParameter) annotation;
+
+ expandedMethods = expandedMethods.flatMap(
+ i -> applyIntTestParameter(i, intTestParameter));
+ }
+ }
+
+ if (!hasParameterised) {
+ throw new IllegalStateException(
+ "Parameter " + parameter + " must be annotated as parameterised");
+ }
+ }
+
+ return expandedMethods;
+ }
+
+ private static Stream<FrameworkMethod> applyStringTestParameter(FrameworkMethod frameworkMethod,
+ StringTestParameter stringTestParameter) {
+ return Stream.of(stringTestParameter.value()).map(
+ (i) -> new FrameworkMethodWithParameter(frameworkMethod, i)
+ );
+ }
+
+ private static Stream<FrameworkMethod> applyIntTestParameter(FrameworkMethod frameworkMethod,
+ IntTestParameter intTestParameter) {
+ return Arrays.stream(intTestParameter.value()).mapToObj(
+ (i) -> new FrameworkMethodWithParameter(frameworkMethod, i)
+ );
+ }
+
+ /**
+ * Sort methods so that methods with identical bedstead annotations are together.
+ *
+ * <p>This will also ensure that all tests methods which are not annotated for bedstead will
+ * run before any tests which are annotated.
+ */
+ private void sortMethodsByBedsteadAnnotations(List<FrameworkMethod> modifiedTests) {
+ List<Annotation> bedsteadAnnotationsSortedByMostCommon =
+ bedsteadAnnotationsSortedByMostCommon(modifiedTests);
+
+ modifiedTests.sort((o1, o2) -> {
+ for (Annotation annotation : bedsteadAnnotationsSortedByMostCommon) {
+ boolean o1HasAnnotation = o1.getAnnotation(annotation.annotationType()) != null;
+ boolean o2HasAnnotation = o2.getAnnotation(annotation.annotationType()) != null;
+
+ if (o1HasAnnotation && !o2HasAnnotation) {
+ // o1 goes to the end
+ return 1;
+ } else if (o2HasAnnotation && !o1HasAnnotation) {
+ return -1;
+ }
+ }
+ return 0;
+ });
+ }
+
+ private List<Annotation> bedsteadAnnotationsSortedByMostCommon(List<FrameworkMethod> methods) {
+ Map<Annotation, Integer> annotationCounts = countAnnotations(methods);
+ List<Annotation> annotations = new ArrayList<>(annotationCounts.keySet());
+
+ annotations.removeIf(
+ annotation ->
+ !annotation.annotationType()
+ .getCanonicalName().contains(BEDSTEAD_PACKAGE_NAME));
+
+ annotations.sort(Comparator.comparingInt(annotationCounts::get));
+ Collections.reverse(annotations);
+
+ return annotations;
+ }
+
+ private Map<Annotation, Integer> countAnnotations(List<FrameworkMethod> methods) {
+ Map<Annotation, Integer> annotationCounts = new HashMap<>();
+
+ for (FrameworkMethod method : methods) {
+ for (Annotation annotation : method.getAnnotations()) {
+ annotationCounts.put(
+ annotation, annotationCounts.getOrDefault(annotation, 0) + 1);
+ }
+ }
+
+ return annotationCounts;
+ }
+
+ private Set<Annotation> getParameterizedAnnotations(FrameworkMethod method) {
+ Set<Annotation> parameterizedAnnotations = new HashSet<>();
+ List<Annotation> annotations = new ArrayList<>(Arrays.asList(method.getAnnotations()));
+
+ // TODO(scottjonathan): We're doing this twice... does it matter?
+ parseEnterpriseAnnotations(annotations);
+
+ for (Annotation annotation : annotations) {
+ if (isParameterizedAnnotation(annotation)) {
+ parameterizedAnnotations.add(annotation);
+ }
+ }
+
+ return parameterizedAnnotations;
+ }
+
+ /**
+ * Parse enterprise-specific annotations.
+ *
+ * <p>To be used before general annotation processing.
+ */
+ static void parseEnterpriseAnnotations(List<Annotation> annotations) {
+ int index = 0;
+ while (index < annotations.size()) {
+ Annotation annotation = annotations.get(index);
+ if (annotation instanceof PositivePolicyTest) {
+ annotations.remove(index);
+ Class<?> policy = ((PositivePolicyTest) annotation).policy();
+
+ EnterprisePolicy enterprisePolicy =
+ policy.getAnnotation(EnterprisePolicy.class);
+ List<Annotation> replacementAnnotations =
+ Policy.positiveStates(policy.getName(), enterprisePolicy);
+ replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
+
+ annotations.addAll(index, replacementAnnotations);
+ index += replacementAnnotations.size();
+ } else if (annotation instanceof NegativePolicyTest) {
+ annotations.remove(index);
+ Class<?> policy = ((NegativePolicyTest) annotation).policy();
+
+ EnterprisePolicy enterprisePolicy =
+ policy.getAnnotation(EnterprisePolicy.class);
+ List<Annotation> replacementAnnotations =
+ Policy.negativeStates(policy.getName(), enterprisePolicy);
+ replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
+
+ annotations.addAll(index, replacementAnnotations);
+ index += replacementAnnotations.size();
+ } else if (annotation instanceof CannotSetPolicyTest) {
+ annotations.remove(index);
+ Class<?> policy = ((CannotSetPolicyTest) annotation).policy();
+
+ EnterprisePolicy enterprisePolicy =
+ policy.getAnnotation(EnterprisePolicy.class);
+ List<Annotation> replacementAnnotations =
+ Policy.cannotSetPolicyStates(policy.getName(), enterprisePolicy,
+ ((CannotSetPolicyTest) annotation).includeDeviceAdminStates(),
+ ((CannotSetPolicyTest) annotation).includeNonDeviceAdminStates());
+ replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
+
+ annotations.addAll(index, replacementAnnotations);
+ index += replacementAnnotations.size();
+ } else if (annotation instanceof CanSetPolicyTest) {
+ annotations.remove(index);
+ Class<?> policy = ((CanSetPolicyTest) annotation).policy();
+ boolean singleTestOnly = ((CanSetPolicyTest) annotation).singleTestOnly();
+
+ EnterprisePolicy enterprisePolicy =
+ policy.getAnnotation(EnterprisePolicy.class);
+ List<Annotation> replacementAnnotations =
+ Policy.canSetPolicyStates(
+ policy.getName(), enterprisePolicy, singleTestOnly);
+ replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
+
+ annotations.addAll(index, replacementAnnotations);
+ index += replacementAnnotations.size();
+ } else {
+ index++;
+ }
+ }
+ }
+
+ @Override
+ protected List<TestRule> classRules() {
+ List<TestRule> rules = super.classRules();
+
+ for (TestRule rule : rules) {
+ if (rule instanceof HarrierRule) {
+ HarrierRule harrierRule = (HarrierRule) rule;
+
+ harrierRule.setSkipTestTeardown(true);
+ harrierRule.setUsingBedsteadJUnit4(true);
+
+ break;
+ }
+ }
+
+ return rules;
+ }
+
+ /**
+ * True if the test is running in debug mode.
+ *
+ * <p>This will result in additional debugging information being added which would otherwise
+ * be dropped to improve test performance.
+ *
+ * <p>To enable this, pass the "bedstead-debug" instrumentation arg as "true"
+ */
+ public static boolean isDebug() {
+ try {
+ Class instrumentationRegistryClass = Class.forName(
+ "androidx.test.platform.app.InstrumentationRegistry");
+
+ Object arguments = instrumentationRegistryClass.getMethod("getArguments")
+ .invoke(null);
+ return Boolean.parseBoolean((String) arguments.getClass()
+ .getMethod("getString", String.class, String.class)
+ .invoke(arguments, "bedstead-debug", "false"));
+ } catch (ClassNotFoundException e) {
+ return false; // Must be on the host so can't access debug information
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ throw new IllegalStateException("Error getting isDebug", e);
+ }
+ }
+
+ @Override
+ protected void validateTestMethods(List<Throwable> errors) {
+ // We do allow arguments - they will fail validation later on if not properly annotated
+ }
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/Defaults.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/Defaults.java
new file mode 100644
index 0000000..13320bc
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/Defaults.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier;
+
+/** Default values used across Harrier. */
+public final class Defaults {
+
+ private Defaults() {
+
+ }
+
+ /**
+ * The password to be used in tests.
+ *
+ * The infrastructure will use this password when a password exists and is required.
+ */
+ public static final String DEFAULT_PASSWORD = "1234";
+
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DynamicParameterizedAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/DynamicParameterizedAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DynamicParameterizedAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/DynamicParameterizedAnnotation.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/FrameworkMethodWithParameter.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/FrameworkMethodWithParameter.java
new file mode 100644
index 0000000..4056890
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/FrameworkMethodWithParameter.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * A {@link FrameworkMethod} which forwards calls to a wrapped {@link FrameworkMethod} except
+ * that it injects a parameter and adds the parameter to the name.
+ */
+public final class FrameworkMethodWithParameter extends FrameworkMethod {
+
+ private final FrameworkMethod mWrappedFrameworkMethod;
+ private final Object mInjectedParam;
+
+ public FrameworkMethodWithParameter(FrameworkMethod frameworkMethod, Object injectedParam) {
+ super(frameworkMethod.getMethod());
+ mWrappedFrameworkMethod = frameworkMethod;
+ mInjectedParam = injectedParam;
+ }
+
+ @Override
+ public boolean isStatic() {
+ if (mWrappedFrameworkMethod == null) {
+ return super.isStatic();
+ }
+ return mWrappedFrameworkMethod.isStatic();
+ }
+
+ @Override
+ public boolean isPublic() {
+ if (mWrappedFrameworkMethod == null) {
+ return super.isPublic();
+ }
+ return mWrappedFrameworkMethod.isPublic();
+ }
+
+ @Override
+ public Method getMethod() {
+ return mWrappedFrameworkMethod.getMethod();
+ }
+
+ @Override
+ public Object invokeExplosively(Object target, Object... params) throws Throwable {
+ Object[] allParams = params;
+ if (mInjectedParam != null) {
+ allParams = new Object[1 + params.length];
+ allParams[0] = mInjectedParam;
+ System.arraycopy(params, 0, allParams, 1, params.length);
+ }
+
+ return mWrappedFrameworkMethod.invokeExplosively(target, allParams);
+ }
+
+ @Override
+ public String getName() {
+ if (mInjectedParam != null) {
+ return mWrappedFrameworkMethod.getName() + "[" + mInjectedParam + "]";
+ }
+ return mWrappedFrameworkMethod.getName();
+ }
+
+ @Override
+ public void validatePublicVoidNoArg(boolean isStatic, List<Throwable> errors) {
+ mWrappedFrameworkMethod.validatePublicVoidNoArg(isStatic, errors);
+ }
+
+ @Override
+ public void validatePublicVoid(boolean isStatic, List<Throwable> errors) {
+ mWrappedFrameworkMethod.validatePublicVoid(isStatic, errors);
+ }
+
+ @Override
+ public Class<?> getReturnType() {
+ return mWrappedFrameworkMethod.getReturnType();
+ }
+
+ @Override
+ public Class<?> getType() {
+ return mWrappedFrameworkMethod.getType();
+ }
+
+ @Override
+ public Class<?> getDeclaringClass() {
+ return mWrappedFrameworkMethod.getDeclaringClass();
+ }
+
+ @Override
+ public void validateNoTypeParametersOnArgs(List<Throwable> errors) {
+ mWrappedFrameworkMethod.validateNoTypeParametersOnArgs(errors);
+ }
+
+ @Override
+ public boolean isShadowedBy(FrameworkMethod other) {
+ return mWrappedFrameworkMethod.isShadowedBy(other);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return mWrappedFrameworkMethod.equals(obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return mWrappedFrameworkMethod.hashCode();
+ }
+
+ @Override
+ public boolean producesType(Type type) {
+ return mWrappedFrameworkMethod.producesType(type);
+ }
+
+ @Override
+ public Annotation[] getAnnotations() {
+ return mWrappedFrameworkMethod.getAnnotations();
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
+ return mWrappedFrameworkMethod.getAnnotation(annotationType);
+ }
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/HarrierRule.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/HarrierRule.java
new file mode 100644
index 0000000..35bc955
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/HarrierRule.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier;
+
+import org.junit.rules.TestRule;
+
+/** A @Rule used on device by Harrier. */
+public abstract class HarrierRule implements TestRule {
+ /** Set that we should skip tearing down between tests. */
+ abstract void setSkipTestTeardown(boolean skipTestTeardown);
+ /** Set that we are using the BedsteadJUnit4 test runner. */
+ abstract void setUsingBedsteadJUnit4(boolean usingBedsteadJUnit4);
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/OptionalBoolean.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/OptionalBoolean.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/OptionalBoolean.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/OptionalBoolean.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/Policy.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/Policy.java
new file mode 100644
index 0000000..d7ea1bd
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/Policy.java
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_AFFILIATED_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_AFFILIATED_PROFILE_OWNER_PROFILE;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_AFFILIATED_PROFILE_OWNER_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PARENT_INSTANCE_OF_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_IN_BACKGROUND;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_AFFILIATED_OTHER_USERS;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_PARENT;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_UNAFFILIATED_OTHER_USERS;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.CAN_BE_DELEGATED;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.DO_NOT_APPLY_TO_NEGATIVE_TESTS;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.NO;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_APP_RESTRICTIONS;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_BLOCK_UNINSTALL;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_CERT_INSTALL;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_CERT_SELECTION;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_ENABLE_SYSTEM_APP;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_INSTALL_EXISTING_PACKAGE;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_KEEP_UNINSTALLED_PACKAGES;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_NETWORK_LOGGING;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_PACKAGE_ACCESS;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_PERMISSION_GRANT;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_SECURITY_LOGGING;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDelegate;
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeNone;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnAffiliatedDeviceOwnerSecondaryUser;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnAffiliatedProfileOwnerSecondaryUser;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnBackgroundDeviceOwnerUser;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnDeviceOwnerUser;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnParentOfCorporateOwnedProfileOwner;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnParentOfProfileOwnerUsingParentInstance;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnProfileOwnerPrimaryUser;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnProfileOwnerProfileWithNoDeviceOwner;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnUnaffiliatedProfileOwnerSecondaryUser;
+
+import com.google.auto.value.AutoAnnotation;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Utility class for enterprise policy tests.
+ */
+public final class Policy {
+
+ // Delegate scopes to be used for a "CannotSet" state. All delegate scopes except the ones which
+ // should allow use of the API will be granted
+ private static final ImmutableSet<String> ALL_DELEGATE_SCOPES = ImmutableSet.of(
+ DELEGATION_CERT_INSTALL,
+ DELEGATION_APP_RESTRICTIONS,
+ DELEGATION_BLOCK_UNINSTALL,
+ DELEGATION_PERMISSION_GRANT,
+ DELEGATION_PACKAGE_ACCESS,
+ DELEGATION_ENABLE_SYSTEM_APP,
+ DELEGATION_INSTALL_EXISTING_PACKAGE,
+ DELEGATION_KEEP_UNINSTALLED_PACKAGES,
+ DELEGATION_NETWORK_LOGGING,
+ DELEGATION_CERT_SELECTION,
+ DELEGATION_SECURITY_LOGGING
+ );
+
+ // This is a map containing all Include* annotations and the flags which lead to them
+ // This is not validated - every state must have a single APPLIED_BY annotation
+ private static final ImmutableMap<Integer, Function<EnterprisePolicy, Set<Annotation>>>
+ STATE_ANNOTATIONS =
+ ImmutableMap.<Integer, Function<EnterprisePolicy, Set<Annotation>>>builder()
+ .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnDeviceOwnerUser()))
+ .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnDeviceOwnerUser(), /* isPrimary= */ true))
+ .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIES_IN_BACKGROUND, singleAnnotation(includeRunOnBackgroundDeviceOwnerUser()))
+ .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIES_IN_BACKGROUND | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnBackgroundDeviceOwnerUser(), /* isPrimary= */ true))
+
+ .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_UNAFFILIATED_OTHER_USERS, singleAnnotation(includeRunOnNonAffiliatedDeviceOwnerSecondaryUser()))
+ .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_UNAFFILIATED_OTHER_USERS | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnNonAffiliatedDeviceOwnerSecondaryUser(), /* isPrimary= */ true))
+ .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_AFFILIATED_OTHER_USERS, singleAnnotation(includeRunOnAffiliatedDeviceOwnerSecondaryUser()))
+ .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_AFFILIATED_OTHER_USERS | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnAffiliatedDeviceOwnerSecondaryUser(), /* isPrimary= */ true))
+
+ .put(APPLIED_BY_AFFILIATED_PROFILE_OWNER_USER | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnAffiliatedProfileOwnerSecondaryUser()))
+ .put(APPLIED_BY_AFFILIATED_PROFILE_OWNER_USER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnAffiliatedProfileOwnerSecondaryUser(), /* isPrimary= */ true))
+ .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnUnaffiliatedProfileOwnerSecondaryUser()))
+ .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnUnaffiliatedProfileOwnerSecondaryUser(), /* isPrimary= */ true))
+ .put(APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnProfileOwnerPrimaryUser()))
+ .put(APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnProfileOwnerPrimaryUser(), /* isPrimary= */ true))
+
+ .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnProfileOwnerProfileWithNoDeviceOwner()))
+ .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnProfileOwnerProfileWithNoDeviceOwner(), /* isPrimary= */ true))
+ .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_PARENT, singleAnnotation(includeRunOnParentOfProfileOwnerWithNoDeviceOwner()))
+ .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_PARENT | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnParentOfProfileOwnerWithNoDeviceOwner(), /* isPrimary= */ true))
+
+ .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_UNAFFILIATED_OTHER_USERS, singleAnnotation(includeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile()))
+ .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_UNAFFILIATED_OTHER_USERS | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile(), /* isPrimary= */ true))
+
+ .put(APPLIED_BY_PARENT_INSTANCE_OF_PROFILE_OWNER | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnParentOfProfileOwnerUsingParentInstance()))
+
+ .build();
+ // This must contain one key for every APPLIED_BY that is being used, and maps to the
+ // "default" for testing that DPC type
+ // in general this will be a state which runs on the same user as the dpc.
+ private static final ImmutableMap<Integer, Function<EnterprisePolicy, Set<Annotation>>>
+ DPC_STATE_ANNOTATIONS_BASE =
+ ImmutableMap.<Integer, Function<EnterprisePolicy, Set<Annotation>>>builder()
+ .put(APPLIED_BY_DEVICE_OWNER, (flags) -> hasFlag(flags.dpc(), APPLIED_BY_DEVICE_OWNER | APPLIES_IN_BACKGROUND) ? ImmutableSet.of(includeRunOnBackgroundDeviceOwnerUser()) : ImmutableSet.of(includeRunOnDeviceOwnerUser()))
+ .put(APPLIED_BY_AFFILIATED_PROFILE_OWNER, singleAnnotation(includeRunOnAffiliatedProfileOwnerSecondaryUser()))
+ .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER, singleAnnotation(includeRunOnProfileOwnerPrimaryUser()))
+ .put(APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO, singleAnnotation(includeRunOnProfileOwnerPrimaryUser()))
+ .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE, singleAnnotation(includeRunOnProfileOwnerProfileWithNoDeviceOwner()))
+ .build();
+ private static final Map<Integer, Function<EnterprisePolicy, Set<Annotation>>>
+ DPC_STATE_ANNOTATIONS = DPC_STATE_ANNOTATIONS_BASE.entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, Policy::addGeneratedStates));
+ private static final int APPLIED_BY_FLAGS =
+ APPLIED_BY_DEVICE_OWNER | APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE
+ | APPLIED_BY_AFFILIATED_PROFILE_OWNER_PROFILE
+ | APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER
+ | APPLIED_BY_AFFILIATED_PROFILE_OWNER_USER
+ | APPLIED_BY_PARENT_INSTANCE_OF_PROFILE_OWNER;
+ private static final Map<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>>
+ ANNOTATIONS_MAP = calculateAnnotationsMap(STATE_ANNOTATIONS);
+
+ private Policy() {
+
+ }
+
+ @AutoAnnotation
+ private static IncludeNone includeNone() {
+ return new AutoAnnotation_Policy_includeNone();
+ }
+
+ @AutoAnnotation
+ private static IncludeRunOnDeviceOwnerUser includeRunOnDeviceOwnerUser() {
+ return new AutoAnnotation_Policy_includeRunOnDeviceOwnerUser();
+ }
+
+ @AutoAnnotation
+ private static IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser includeRunOnNonAffiliatedDeviceOwnerSecondaryUser() {
+ return new AutoAnnotation_Policy_includeRunOnNonAffiliatedDeviceOwnerSecondaryUser();
+ }
+
+ @AutoAnnotation
+ private static IncludeRunOnAffiliatedDeviceOwnerSecondaryUser includeRunOnAffiliatedDeviceOwnerSecondaryUser() {
+ return new AutoAnnotation_Policy_includeRunOnAffiliatedDeviceOwnerSecondaryUser();
+ }
+
+ @AutoAnnotation
+ private static IncludeRunOnAffiliatedProfileOwnerSecondaryUser includeRunOnAffiliatedProfileOwnerSecondaryUser() {
+ return new AutoAnnotation_Policy_includeRunOnAffiliatedProfileOwnerSecondaryUser();
+ }
+
+ @AutoAnnotation
+ private static IncludeRunOnUnaffiliatedProfileOwnerSecondaryUser includeRunOnUnaffiliatedProfileOwnerSecondaryUser() {
+ return new AutoAnnotation_Policy_includeRunOnUnaffiliatedProfileOwnerSecondaryUser();
+ }
+
+ @AutoAnnotation
+ private static IncludeRunOnProfileOwnerProfileWithNoDeviceOwner includeRunOnProfileOwnerProfileWithNoDeviceOwner() {
+ return new AutoAnnotation_Policy_includeRunOnProfileOwnerProfileWithNoDeviceOwner();
+ }
+
+ @AutoAnnotation
+ private static IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile includeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile() {
+ return new AutoAnnotation_Policy_includeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile();
+ }
+
+ @AutoAnnotation
+ private static IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner includeRunOnParentOfProfileOwnerWithNoDeviceOwner() {
+ return new AutoAnnotation_Policy_includeRunOnParentOfProfileOwnerWithNoDeviceOwner();
+ }
+
+ @AutoAnnotation
+ private static IncludeRunOnParentOfCorporateOwnedProfileOwner includeRunOnParentOfCorporateOwnedProfileOwner() {
+ return new AutoAnnotation_Policy_includeRunOnParentOfCorporateOwnedProfileOwner();
+ }
+
+ @AutoAnnotation
+ private static IncludeRunOnProfileOwnerPrimaryUser includeRunOnProfileOwnerPrimaryUser() {
+ return new AutoAnnotation_Policy_includeRunOnProfileOwnerPrimaryUser();
+ }
+
+ @AutoAnnotation
+ private static IncludeRunOnBackgroundDeviceOwnerUser includeRunOnBackgroundDeviceOwnerUser() {
+ return new AutoAnnotation_Policy_includeRunOnBackgroundDeviceOwnerUser();
+ }
+
+ @AutoAnnotation
+ private static EnsureHasDelegate ensureHasDelegate(EnsureHasDelegate.AdminType admin,
+ String[] scopes, boolean isPrimary) {
+ return new AutoAnnotation_Policy_ensureHasDelegate(admin, scopes, isPrimary);
+ }
+
+ @AutoAnnotation
+ private static IncludeRunOnParentOfProfileOwnerUsingParentInstance includeRunOnParentOfProfileOwnerUsingParentInstance() {
+ return new AutoAnnotation_Policy_includeRunOnParentOfProfileOwnerUsingParentInstance();
+ }
+
+ private static Function<EnterprisePolicy, Set<Annotation>> singleAnnotation(
+ Annotation annotation) {
+ return (i) -> ImmutableSet.of(annotation);
+ }
+
+ private static Function<EnterprisePolicy, Set<Annotation>> generateDelegateAnnotation(
+ Annotation annotation, boolean isPrimary) {
+ return (policy) -> {
+ Annotation[] existingAnnotations = annotation.annotationType().getAnnotations();
+ return Arrays.stream(policy.delegatedScopes())
+ .map(scope -> {
+ Annotation[] newAnnotations = Arrays.copyOf(existingAnnotations,
+ existingAnnotations.length + 1);
+ newAnnotations[newAnnotations.length - 1] = ensureHasDelegate(
+ EnsureHasDelegate.AdminType.PRIMARY, new String[]{scope},
+ isPrimary);
+
+ return new DynamicParameterizedAnnotation(
+ annotation.annotationType().getSimpleName() + "Delegate:" + scope,
+ newAnnotations);
+ }).collect(Collectors.toSet());
+ };
+ }
+
+ private static Map<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>> calculateAnnotationsMap(
+ Map<Integer, Function<EnterprisePolicy, Set<Annotation>>> annotations) {
+ Map<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>> b = new HashMap<>();
+
+ for (Map.Entry<Integer, Function<EnterprisePolicy, Set<Annotation>>> i :
+ annotations.entrySet()) {
+ if (!b.containsKey(i.getValue())) {
+ b.put(i.getValue(), new HashSet<>());
+ }
+
+ b.get(i.getValue()).add(i.getKey());
+ }
+
+ return b;
+ }
+
+ private static Function<EnterprisePolicy, Set<Annotation>> addGeneratedStates(
+ ImmutableMap.Entry<Integer, Function<EnterprisePolicy, Set<Annotation>>> entry) {
+ return (policy) -> {
+ if (hasFlag(policy.dpc(), entry.getKey() | CAN_BE_DELEGATED)) {
+ Set<Annotation> results = new HashSet<>(entry.getValue().apply(policy));
+ results.addAll(results.stream().flatMap(
+ t -> generateDelegateAnnotation(t, /* isPrimary= */ true).apply(
+ policy).stream())
+ .collect(Collectors.toSet()));
+
+ return results;
+ }
+
+ return entry.getValue().apply(policy);
+ };
+ }
+
+
+ /**
+ * Get parameterized test runs for the given policy.
+ *
+ * <p>These are states which should be run where the policy is able to be applied.
+ */
+ public static List<Annotation> positiveStates(String policyName,
+ EnterprisePolicy enterprisePolicy) {
+ Set<Annotation> annotations = new HashSet<>();
+
+ validateFlags(policyName, enterprisePolicy.dpc());
+
+ for (Map.Entry<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>> annotation :
+ ANNOTATIONS_MAP.entrySet()) {
+ if (isPositive(enterprisePolicy.dpc(), annotation.getValue())) {
+ annotations.addAll(annotation.getKey().apply(enterprisePolicy));
+ }
+ }
+
+ if (annotations.isEmpty()) {
+ // Don't run the original test unparameterized
+ annotations.add(includeNone());
+ }
+
+ return new ArrayList<>(annotations);
+ }
+
+ private static boolean isPositive(int[] policyFlags, Set<Integer> annotationFlags) {
+ for (int annotationFlag : annotationFlags) {
+ if (hasFlag(policyFlags, annotationFlag)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isNegative(int[] policyFlags, Set<Integer> annotationFlags) {
+ for (int annotationFlag : annotationFlags) {
+ if (hasFlag(annotationFlag, DO_NOT_APPLY_TO_NEGATIVE_TESTS, /* nonMatchingFlag= */
+ NO)) {
+ return false; // We don't support using this annotation for negative tests
+ }
+
+ int appliedByFlag = APPLIED_BY_FLAGS & annotationFlag;
+ int otherFlags = annotationFlag ^ appliedByFlag; // remove the appliedByFlag
+ if (hasFlag(policyFlags, /* matchingFlag= */ appliedByFlag, /* nonMatchingFlag= */
+ otherFlags)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get negative parameterized test runs for the given policy.
+ *
+ * <p>These are states which should be run where the policy is not able to be applied.
+ */
+ public static List<Annotation> negativeStates(String policyName,
+ EnterprisePolicy enterprisePolicy) {
+ Set<Annotation> annotations = new HashSet<>();
+
+ validateFlags(policyName, enterprisePolicy.dpc());
+
+ for (Map.Entry<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>> annotation :
+ ANNOTATIONS_MAP.entrySet()) {
+ if (isNegative(enterprisePolicy.dpc(), annotation.getValue())) {
+ annotations.addAll(annotation.getKey().apply(enterprisePolicy));
+ }
+ }
+
+ if (annotations.isEmpty()) {
+ // Don't run the original test unparameterized
+ annotations.add(includeNone());
+ }
+
+ return new ArrayList<>(annotations);
+ }
+
+ /**
+ * Get parameterized test runs where the policy cannot be set for the given policy.
+ */
+ public static List<Annotation> cannotSetPolicyStates(String policyName,
+ EnterprisePolicy enterprisePolicy, boolean includeDeviceAdminStates,
+ boolean includeNonDeviceAdminStates) {
+ Set<Annotation> annotations = new HashSet<>();
+
+ validateFlags(policyName, enterprisePolicy.dpc());
+
+ if (includeDeviceAdminStates) {
+ int allFlags = 0;
+ for (int p : enterprisePolicy.dpc()) {
+ allFlags = allFlags | p;
+ }
+
+ for (Map.Entry<Integer, Function<EnterprisePolicy, Set<Annotation>>> appliedByFlag :
+ DPC_STATE_ANNOTATIONS.entrySet()) {
+ if ((appliedByFlag.getKey() & allFlags) == 0) {
+ annotations.addAll(appliedByFlag.getValue().apply(enterprisePolicy));
+ }
+ }
+ }
+
+ if (includeNonDeviceAdminStates) {
+ Set<String> validScopes = ImmutableSet.copyOf(enterprisePolicy.delegatedScopes());
+ String[] scopes = ALL_DELEGATE_SCOPES.stream()
+ .filter(i -> !validScopes.contains(i))
+ .toArray(String[]::new);
+ Annotation[] existingAnnotations = IncludeRunOnDeviceOwnerUser.class.getAnnotations();
+
+ if (BedsteadJUnit4.isDebug()) {
+ // Add a non-DPC with no delegate scopes
+ Annotation[] newAnnotations = Arrays.copyOf(existingAnnotations,
+ existingAnnotations.length + 1);
+ newAnnotations[newAnnotations.length - 1] = ensureHasDelegate(
+ EnsureHasDelegate.AdminType.PRIMARY, new String[]{},
+ /* isPrimary= */ true);
+ annotations.add(
+ new DynamicParameterizedAnnotation("DelegateWithNoScopes", newAnnotations));
+
+ for (String scope : scopes) {
+ newAnnotations = Arrays.copyOf(existingAnnotations,
+ existingAnnotations.length + 1);
+ newAnnotations[newAnnotations.length - 1] = ensureHasDelegate(
+ EnsureHasDelegate.AdminType.PRIMARY, new String[]{scope},
+ /* isPrimary= */ true);
+ annotations.add(
+ new DynamicParameterizedAnnotation("DelegateWithScope:" + scope, newAnnotations));
+ }
+ } else {
+ Annotation[] newAnnotations = Arrays.copyOf(existingAnnotations,
+ existingAnnotations.length + 1);
+ newAnnotations[newAnnotations.length - 1] = ensureHasDelegate(
+ EnsureHasDelegate.AdminType.PRIMARY, scopes, /* isPrimary= */ true);
+ annotations.add(
+ new DynamicParameterizedAnnotation("DelegateWithoutValidScope", newAnnotations));
+ }
+ }
+
+ if (annotations.isEmpty()) {
+ // Don't run the original test unparameterized
+ annotations.add(includeNone());
+ }
+
+ return new ArrayList<>(annotations);
+ }
+
+ /**
+ * Get state annotations where the policy can be set for the given policy.
+ */
+ public static List<Annotation> canSetPolicyStates(
+ String policyName, EnterprisePolicy enterprisePolicy, boolean singleTestOnly) {
+ Set<Annotation> annotations = new HashSet<>();
+
+ validateFlags(policyName, enterprisePolicy.dpc());
+
+ int allFlags = 0;
+ for (int p : enterprisePolicy.dpc()) {
+ allFlags = allFlags | p;
+ }
+
+ for (Map.Entry<Integer, Function<EnterprisePolicy, Set<Annotation>>> appliedByFlag :
+ DPC_STATE_ANNOTATIONS.entrySet()) {
+ if ((appliedByFlag.getKey() & allFlags) == appliedByFlag.getKey()) {
+ annotations.addAll(appliedByFlag.getValue().apply(enterprisePolicy));
+ }
+ }
+
+ if (annotations.isEmpty()) {
+ // Don't run the original test unparameterized
+ annotations.add(includeNone());
+ }
+
+ List<Annotation> annotationList = new ArrayList<>(annotations);
+
+ if (singleTestOnly) {
+ // We select one annotation in an arbitrary but deterministic way
+ annotationList.sort(Comparator.comparing(
+ a -> a instanceof DynamicParameterizedAnnotation
+ ? "DynamicParameterizedAnnotation" : a.annotationType().getName()));
+
+ // We don't want a delegate to be the representative test
+ Annotation firstAnnotation = annotationList.stream()
+ .filter(i -> !(i instanceof DynamicParameterizedAnnotation))
+ .findFirst().get();
+ annotationList.clear();
+ annotationList.add(firstAnnotation);
+ }
+
+ return annotationList;
+ }
+
+ private static void validateFlags(String policyName, int[] values) {
+ int usedAppliedByFlags = 0;
+
+ for (int value : values) {
+ validateFlags(policyName, value);
+ int newUsedAppliedByFlags = usedAppliedByFlags | (value & APPLIED_BY_FLAGS);
+ if (newUsedAppliedByFlags == usedAppliedByFlags) {
+ throw new IllegalStateException(
+ "Cannot have more than one policy flag APPLIED by the same component. "
+ + "Error in policy " + policyName);
+ }
+ usedAppliedByFlags = newUsedAppliedByFlags;
+ }
+ }
+
+ private static void validateFlags(String policyName, int value) {
+ int matchingAppliedByFlags = APPLIED_BY_FLAGS & value;
+
+ if (matchingAppliedByFlags == 0) {
+ throw new IllegalStateException(
+ "All policy flags must specify 1 APPLIED_BY flag. Policy " + policyName
+ + " did not.");
+ }
+ }
+
+ private static boolean hasFlag(int[] values, int matchingFlag) {
+ return hasFlag(values, matchingFlag, /* nonMatchingFlag= */ NO);
+ }
+
+ private static boolean hasFlag(int[] values, int matchingFlag, int nonMatchingFlag) {
+ for (int value : values) {
+ if (hasFlag(value, matchingFlag, nonMatchingFlag)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean hasFlag(int value, int matchingFlag, int nonMatchingFlag) {
+ if (!((value & matchingFlag) == matchingFlag)) {
+ return false;
+ }
+
+ if (nonMatchingFlag != NO) {
+ return (value & nonMatchingFlag) != nonMatchingFlag;
+ }
+
+ return true;
+ }
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/UserType.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/UserType.java
new file mode 100644
index 0000000..9dacdae
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/UserType.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier;
+
+public enum UserType {
+ /** Only to be used with annotations. */
+ ANY,
+ SYSTEM_USER,
+ CURRENT_USER,
+ PRIMARY_USER,
+ SECONDARY_USER,
+ WORK_PROFILE,
+ TV_PROFILE,
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/AfterClass.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/AfterClass.java
new file mode 100644
index 0000000..3bb6449
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/AfterClass.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Replacement for {@link org.junit.AfterClass} for use by classes which use {@code Devicestate}.
+ *
+ * <p>Methods annotated {@link AfterClass} must be public, static, must return {@code void}, and
+ * must take no arguments.
+ *
+ * <p>The annotated method will be called after all tests, once per class.
+ *
+ * <p>The state prior to calling this method is not guaranteed, as test methods may have changed the
+ * state. If any class-level annotation assumptions are violated this method will not be run.
+ *
+ * <p>If there are multiple methods annotated {@code @AfterClass} there is no guarantee as to the
+ * order they will be executed.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface AfterClass {
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/AnnotationRunPrecedence.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/AnnotationRunPrecedence.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/AnnotationRunPrecedence.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/AnnotationRunPrecedence.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/BeforeClass.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/BeforeClass.java
new file mode 100644
index 0000000..f6de30a
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/BeforeClass.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Replacement for {@link org.junit.BeforeClass} for use by classes which use {@code Devicestate}.
+ *
+ * <p>Methods annotated {@link BeforeClass} must be public, static, must return {@code void}, and
+ * must take no arguments.
+ *
+ * <p>The annotated method will be called before any tests, once per class. Class-level Bedstead
+ * annotations will be processed before running the method, and if any assumptions are violated the
+ * method will not be run.
+ *
+ * <p>If there are multiple methods annotated {@code @BeforeClass} there is no guarantee as to the
+ * order they will be executed.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface BeforeClass {
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureDoesNotHavePermission.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureDoesNotHavePermission.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureDoesNotHavePermission.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureDoesNotHavePermission.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoSecondaryUser.java
new file mode 100644
index 0000000..7e65661
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoSecondaryUser.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+
+import com.android.bedstead.harrier.annotations.meta.EnsureHasNoUserAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a device which has no secondary user that is not the
+ * instrumented user.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a device which
+ * has no secondary user that is not the current user. Otherwise, you can use {@code Devicestate}
+ * to ensure that the device enters the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@EnsureHasNoUserAnnotation("android.os.usertype.full.SECONDARY")
+public @interface EnsureHasNoSecondaryUser {
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default MIDDLE;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoTvProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoTvProfile.java
new file mode 100644
index 0000000..d4b1c94
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoTvProfile.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+
+import com.android.bedstead.harrier.UserType;
+import com.android.bedstead.harrier.annotations.meta.EnsureHasNoProfileAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a user which has no Tv profile.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a user which has
+ * no Tv profile. Otherwise, you can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@EnsureHasNoProfileAnnotation("com.android.tv.profile")
+public @interface EnsureHasNoTvProfile {
+ /** Which user type the tv profile should not be attached to. */
+ UserType forUser() default CURRENT_USER;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default MIDDLE;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoWorkProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoWorkProfile.java
new file mode 100644
index 0000000..d3693dd
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoWorkProfile.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+
+import com.android.bedstead.harrier.UserType;
+import com.android.bedstead.harrier.annotations.meta.EnsureHasNoProfileAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a user which does not have a work profile.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a user which has
+ * no work profile. Otherwise, you can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@EnsureHasNoProfileAnnotation("android.os.usertype.profile.MANAGED")
+@RequireFeature("android.software.managed_users")
+public @interface EnsureHasNoWorkProfile {
+ /** Which user type the work profile should not be attached to. */
+ UserType forUser() default CURRENT_USER;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default MIDDLE;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasPermission.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasPermission.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasPermission.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasPermission.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java
new file mode 100644
index 0000000..6113175
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.OptionalBoolean.ANY;
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.annotations.meta.EnsureHasUserAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a device which has a secondary user.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a device which
+ * has a secondary user that is not the current user. Otherwise, you can use {@code Devicestate}
+ * to ensure that the device enters the correct state for the method. If there is not already a
+ * secondary user on the device, and the device does not support creating additional users, then
+ * the test will be skipped.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@EnsureHasUserAnnotation("android.os.usertype.full.SECONDARY")
+public @interface EnsureHasSecondaryUser {
+ /** Whether the instrumented test app should be installed in the secondary user. */
+ OptionalBoolean installInstrumentedApp() default TRUE;
+
+ /**
+ * Should we ensure that we are switched to the given user
+ */
+ OptionalBoolean switchedToUser() default ANY;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default MIDDLE;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java
new file mode 100644
index 0000000..aa4fbfe
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.OptionalBoolean.ANY;
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.UserType;
+import com.android.bedstead.harrier.annotations.meta.EnsureHasProfileAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a user which has a Tv profile.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a user which has
+ * a Tv profile. Otherwise, you can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@EnsureHasProfileAnnotation("com.android.tv.profile")
+public @interface EnsureHasTvProfile {
+ /** Which user type the tv profile should be attached to. */
+ UserType forUser() default CURRENT_USER;
+
+ /** Whether the instrumented test app should be installed in the tv profile. */
+ OptionalBoolean installInstrumentedApp() default TRUE;
+
+ /**
+ * Should we ensure that we are switched to the parent of the profile.
+ */
+ OptionalBoolean switchedToParentUser() default ANY;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default MIDDLE;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java
new file mode 100644
index 0000000..99badd8
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.OptionalBoolean.ANY;
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.UserType;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner;
+import com.android.bedstead.harrier.annotations.meta.EnsureHasProfileAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a user which has a work profile.
+ *
+ * <p>Use of this annotation implies
+ * {@code RequireFeature("android.software.managed_users", SKIP)}.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a user which has
+ * a work profile. Otherwise, you can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@EnsureHasProfileAnnotation(value = "android.os.usertype.profile.MANAGED", hasProfileOwner = true)
+@RequireFeature("android.software.managed_users")
+@EnsureHasNoDeviceOwner // TODO: This should only apply on Android R+
+public @interface EnsureHasWorkProfile {
+ /** Which user type the work profile should be attached to. */
+ UserType forUser() default CURRENT_USER;
+
+ /** Whether the instrumented test app should be installed in the work profile. */
+ OptionalBoolean installInstrumentedApp() default TRUE;
+
+ /**
+ * Whether the profile owner's DPC should be returned by calls to {@code Devicestate#dpc()}.
+ *
+ * <p>Only one device policy controller per test should be marked as primary.
+ */
+ boolean dpcIsPrimary() default false;
+
+ /**
+ * If true, uses the {@code DevicePolicyManager#getParentProfileInstance(ComponentName)}
+ * instance of the dpc when calling to .dpc()
+ *
+ * <p>Only used if {@link #dpcIsPrimary()} is true.
+ */
+ boolean useParentInstanceOfDpc() default false;
+
+ /**
+ * Should we ensure that we are switched to the parent of the profile.
+ */
+ OptionalBoolean switchedToParentUser() default ANY;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default MIDDLE;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackageNotInstalled.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackageNotInstalled.java
new file mode 100644
index 0000000..c87abb8
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackageNotInstalled.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run only when a given package is not installed.
+ *
+ * <p>You can guarantee that these methods do not run on devices with the package by
+ * using {@code DeviceState}.
+ *
+ * <p>Tests annotated with this will attempt to remove the package if it exists, or otherwise fail.
+ * If you'd rather skip or fail tests immediately without attempting to remove see
+ * {@link RequirePackageNotInstalled}.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(EnsurePackagesNotInstalled.class)
+public @interface EnsurePackageNotInstalled {
+ String value();
+ UserType onUser() default UserType.CURRENT_USER;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default MIDDLE;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackagesNotInstalled.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackagesNotInstalled.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackagesNotInstalled.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackagesNotInstalled.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePasswordNotSet.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePasswordNotSet.java
new file mode 100644
index 0000000..fe2dd5e
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePasswordNotSet.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method requires no password be set on the given user.
+ *
+ * <p>You can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsurePasswordNotSet {
+
+ /** The user who must not have a password. */
+ UserType forUser() default CURRENT_USER;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePasswordSet.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePasswordSet.java
new file mode 100644
index 0000000..da2a09d
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePasswordSet.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.Defaults.DEFAULT_PASSWORD;
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method requires a password be set on the given user.
+ *
+ * <p>You can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsurePasswordSet {
+
+ /** The user who must have a password. */
+ UserType forUser() default CURRENT_USER;
+
+ /** The password to set. Defaults to {@code Devicestate#DEFAULT_PASSWORD}. */
+ String password() default DEFAULT_PASSWORD;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureScreenIsOn.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureScreenIsOn.java
new file mode 100644
index 0000000..10eb715
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureScreenIsOn.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method requires the screen to be on.
+ *
+ * <p>You can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsureScreenIsOn {
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default MIDDLE;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/FailureMode.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/FailureMode.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/FailureMode.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/FailureMode.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/IntTestParameter.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/IntTestParameter.java
new file mode 100644
index 0000000..dca3d37
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/IntTestParameter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark an {@code int} parameter as being parameterised with the given values.
+ *
+ * <p>You must be using the {@link BedsteadJUnit4} test runner to use this annotation.
+ */
+@Target({ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface IntTestParameter {
+ int[] value();
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/NotificationsTest.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/NotificationsTest.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/NotificationsTest.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/NotificationsTest.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/Postsubmit.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/Postsubmit.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/Postsubmit.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/Postsubmit.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireAospBuild.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireAospBuild.java
new file mode 100644
index 0000000..6c9c008
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireAospBuild.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+import static com.android.bedstead.harrier.annotations.RequireAospBuild.GMS_CORE_PACKAGE;
+import static com.android.bedstead.harrier.annotations.RequireAospBuild.GSF_PACKAGE;
+import static com.android.bedstead.harrier.annotations.RequireAospBuild.PLAY_STORE_PACKAGE;
+
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequirePackageNotInstalled(value = GMS_CORE_PACKAGE, onUser = UserType.ANY)
+@RequirePackageNotInstalled(value = PLAY_STORE_PACKAGE, onUser = UserType.ANY)
+@RequirePackageNotInstalled(value = GSF_PACKAGE, onUser = UserType.ANY)
+public @interface RequireAospBuild {
+ String GMS_CORE_PACKAGE = "com.google.android.gms";
+ String PLAY_STORE_PACKAGE = "com.android.vending";
+ String GSF_PACKAGE = "com.google.android.gsf";
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireCnGmsBuild.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireCnGmsBuild.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireCnGmsBuild.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireCnGmsBuild.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeature.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeature.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeature.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeature.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeatures.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeatures.java
new file mode 100644
index 0000000..2fc40f6
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeatures.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.annotations.meta.RepeatingAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RepeatingAnnotation
+public @interface RequireDoesNotHaveFeatures {
+ RequireDoesNotHaveFeature[] value();
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireFeature.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireFeature.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireFeature.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireFeature.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireFeatures.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireFeatures.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireFeatures.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireFeatures.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsBuild.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsBuild.java
new file mode 100644
index 0000000..86323c0
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsBuild.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+import static com.android.bedstead.harrier.annotations.RequireAospBuild.GMS_CORE_PACKAGE;
+import static com.android.bedstead.harrier.annotations.RequireAospBuild.GSF_PACKAGE;
+import static com.android.bedstead.harrier.annotations.RequireAospBuild.PLAY_STORE_PACKAGE;
+
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequirePackageInstalled(value = GMS_CORE_PACKAGE, onUser = UserType.ANY)
+@RequirePackageInstalled(value = PLAY_STORE_PACKAGE, onUser = UserType.ANY)
+@RequirePackageInstalled(value = GSF_PACKAGE, onUser = UserType.ANY)
+public @interface RequireGmsBuild {
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireHeadlessSystemUserMode.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireHeadlessSystemUserMode.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireHeadlessSystemUserMode.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireHeadlessSystemUserMode.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireLowRamDevice.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireLowRamDevice.java
new file mode 100644
index 0000000..5fb415f
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireLowRamDevice.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to indicate that a test requires a low ram device.
+ *
+ * <p>This can be enforced by using {@code Devicestate}.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireLowRamDevice {
+ String reason();
+ FailureMode failureMode() default FailureMode.SKIP;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireNotCnGmsBuild.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireNotCnGmsBuild.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireNotCnGmsBuild.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireNotCnGmsBuild.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireNotHeadlessSystemUserMode.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireNotHeadlessSystemUserMode.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireNotHeadlessSystemUserMode.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireNotHeadlessSystemUserMode.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireNotLowRamDevice.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireNotLowRamDevice.java
new file mode 100644
index 0000000..5041014
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireNotLowRamDevice.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to indicate that a test does not run on low ram devices.
+ *
+ * <p>This can be enforced by using {@code Devicestate}.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireNotLowRamDevice {
+ String reason();
+ FailureMode failureMode() default FailureMode.SKIP;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageInstalled.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageInstalled.java
new file mode 100644
index 0000000..7cd5717
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageInstalled.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run only when a given package is installed.
+ *
+ * <p>You can guarantee that these methods do not run on devices lacking the package by
+ * using {@code DeviceState}.
+ *
+ * <p>By default the test will be skipped if the package is not available. If you'd rather the test
+ * fail then use {@code failureMode = FailureMode.FAIL}.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(RequirePackagesInstalled.class)
+public @interface RequirePackageInstalled {
+ String value();
+ UserType onUser() default UserType.CURRENT_USER;
+ FailureMode failureMode() default FailureMode.SKIP;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageNotInstalled.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageNotInstalled.java
new file mode 100644
index 0000000..747eb5d
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageNotInstalled.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run only when a given package is not installed.
+ *
+ * <p>You can guarantee that these methods do not run on devices with the package by
+ * using {@code DeviceState}.
+ *
+ * <p>By default the test will be skipped if the package is available. If you'd rather the test
+ * fail then use {@code failureMode = FailureMode.FAIL}. If you'd like to uninstall the package if
+ * it is installed, see {@link EnsurePackageNotInstalled}.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(RequirePackagesNotInstalled.class)
+public @interface RequirePackageNotInstalled {
+ String value();
+ UserType onUser() default UserType.CURRENT_USER;
+ FailureMode failureMode() default FailureMode.SKIP;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackagesInstalled.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackagesInstalled.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackagesInstalled.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackagesInstalled.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackagesNotInstalled.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackagesNotInstalled.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackagesNotInstalled.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackagesNotInstalled.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunNotOnSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunNotOnSecondaryUser.java
new file mode 100644
index 0000000..5e84335
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunNotOnSecondaryUser.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a non-secondary user.
+ *
+ * <p>Your test configuration should be such that this test is only run where a non-secondary user
+ * is created and the test is being run on that user.
+ *
+ * <p>Optionally, you can guarantee that these methods do not run on a secondary user by
+ * using {@code Devicestate}.
+ *
+ * <p>This annotation by default opts a test into multi-user presubmit. New tests should also be
+ * annotated {@link Postsubmit} until they are shown to meet the multi-user presubmit
+ * requirements.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+// To ensure that the test doesn't run on the secondary user we require that the test run on
+// the primary user to ensure consistent behaviour.
+@RequireRunOnPrimaryUser
+public @interface RequireRunNotOnSecondaryUser {
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java
new file mode 100644
index 0000000..3f937af
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.annotations.meta.RequireRunOnUserAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on the primary user.
+ *
+ * <p>Your test configuration should be such that this test is only run on the primary user
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of the primary
+ * user by using {@code Devicestate}.
+ *
+ * <p>Note that in practice this requires that the test runs on the system user, but excludes
+ * headless system users. To mark that a test should run on the system user, including headless
+ * system users, see {@link RequireRunOnSystemUser}.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequireRunOnUserAnnotation("android.os.usertype.full.SYSTEM")
+public @interface RequireRunOnPrimaryUser {
+ /**
+ * Should we ensure that we are switched to the given user
+ */
+ OptionalBoolean switchedToUser() default TRUE;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java
new file mode 100644
index 0000000..08734ea
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.annotations.meta.RequireRunOnUserAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a secondary user.
+ *
+ * <p>Your test configuration should be such that this test is only run where a secondary user is
+ * created and the test is being run on that user.
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of a secondary user by
+ * using {@code Devicestate}.
+ *
+ * <p>This annotation by default opts a test into multi-user presubmit. New tests should also be
+ * annotated {@link Postsubmit} until they are shown to meet the multi-user presubmit
+ * requirements.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequireRunOnUserAnnotation("android.os.usertype.full.SECONDARY")
+public @interface RequireRunOnSecondaryUser {
+ /**
+ * Should we ensure that we are switched to the given user
+ */
+ OptionalBoolean switchedToUser() default TRUE;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSystemUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSystemUser.java
new file mode 100644
index 0000000..03321ce
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSystemUser.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.annotations.meta.RequireRunOnUserAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on the system user.
+ *
+ * <p>Your test configuration should be such that this test is only run on the system user
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of the system
+ * user by using {@code Devicestate}.
+ *
+ * <p>Note that this requires that the test runs on the system user, including headless system
+ * users. To mark that a test should run on the primary user, excluding headless
+ * system users, see {@link RequireRunOnPrimaryUser}.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequireRunOnUserAnnotation(
+ {"android.os.usertype.full.SYSTEM", "android.os.usertype.system.HEADLESS"})
+public @interface RequireRunOnSystemUser {
+ /**
+ * Should we ensure that we are switched to the given user
+ */
+ OptionalBoolean switchedToUser() default TRUE;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java
new file mode 100644
index 0000000..b5ab38c
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.OptionalBoolean.ANY;
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.annotations.meta.RequireRunOnProfileAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run within a Tv profile.
+ *
+ * <p>Your test configuration should be such that this test is only run where a Tv profile is
+ * created and the test is being run within that user.
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of a Tv
+ * profile by using {@code Devicestate}.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequireRunOnProfileAnnotation("com.android.tv.profile")
+public @interface RequireRunOnTvProfile {
+ OptionalBoolean installInstrumentedAppInParent() default ANY;
+
+ /**
+ * Should we ensure that we are switched to the parent of the profile.
+ */
+ OptionalBoolean switchedToParentUser() default TRUE;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java
new file mode 100644
index 0000000..b811fff
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.OptionalBoolean.ANY;
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+import static com.android.bedstead.nene.packages.CommonPackages.FEATURE_DEVICE_ADMIN;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasProfileOwner;
+import com.android.bedstead.harrier.annotations.meta.RequireRunOnProfileAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run within a work profile.
+ *
+ * <p>Your test configuration should be such that this test is only run where a work profile is
+ * created and the test is being run within that user.
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of a work
+ * profile by using {@code Devicestate}.
+ *
+ * <p>This annotation by default opts a test into multi-user presubmit. New tests should also be
+ * annotated {@link Postsubmit} until they are shown to meet the multi-user presubmit requirements.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequireRunOnProfileAnnotation(value = "android.os.usertype.profile.MANAGED",
+ hasProfileOwner = true)
+@EnsureHasProfileOwner
+@RequireFeature(FEATURE_DEVICE_ADMIN)
+public @interface RequireRunOnWorkProfile {
+ OptionalBoolean installInstrumentedAppInParent() default ANY;
+
+ /**
+ * Whether the profile owner's DPC should be returned by calls to {@code Devicestate#dpc()}.
+ *
+ * <p>Only one device policy controller per test should be marked as primary.
+ */
+ boolean dpcIsPrimary() default false;
+
+ /**
+ * Affiliation ids to be set for the profile owner.
+ */
+ String[] affiliationIds() default {};
+
+ /**
+ * Should we ensure that we are switched to the parent of the profile.
+ */
+ OptionalBoolean switchedToParentUser() default TRUE;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireSdkVersion.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireSdkVersion.java
new file mode 100644
index 0000000..75cbed0
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireSdkVersion.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should only run on specified sdk versions.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a device with the
+ * given version. Otherwise, you can use {@code Devicestate} to ensure that the test is
+ * not run when the sdk version is not correct.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireSdkVersion {
+ int min() default 1;
+ int max() default Integer.MAX_VALUE;
+ String reason() default "";
+ FailureMode failureMode() default FailureMode.SKIP;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireTargetSdkVersion.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireTargetSdkVersion.java
new file mode 100644
index 0000000..81bd147
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireTargetSdkVersion.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should only run in an apk targeting the specified sdk version.
+ *
+ * <p>Your test configuration may be configured so that this test is only run in an apk with the
+ * given version. Otherwise, you can use {@code Devicestate} to ensure that the test is
+ * not run when the target sdk version is not correct.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireTargetSdkVersion {
+ int min() default 1;
+ int max() default Integer.MAX_VALUE;
+ FailureMode failureMode() default FailureMode.SKIP;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireUserSupported.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireUserSupported.java
new file mode 100644
index 0000000..e792d76
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireUserSupported.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should only run if a particular user type is supported
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a user which
+ * supports the user types. Otherwise, you can use {@code Devicestate} to ensure that the test is
+ * not run when the user type is not supported.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(RequireUsersSupported.class)
+public @interface RequireUserSupported {
+ String value();
+ FailureMode failureMode() default FailureMode.SKIP;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireUsersSupported.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireUsersSupported.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireUsersSupported.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireUsersSupported.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequiresNonGrantablePermission.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequiresNonGrantablePermission.java
new file mode 100644
index 0000000..e75903c
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequiresNonGrantablePermission.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test is not in CTS only because it requires a permission that cannot be granted in
+ * CTS.
+ *
+ * <p>This has no impact on running tests, and is just for information for future maintainers.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequiresNonGrantablePermission {
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/SlowApiTest.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/SlowApiTest.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/SlowApiTest.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/SlowApiTest.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/StringTestParameter.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/StringTestParameter.java
new file mode 100644
index 0000000..cbf7bdb
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/StringTestParameter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark a {@link String} parameter as being parameterised with the given values.
+ *
+ * <p>You must be using the {@link BedsteadJUnit4} test runner to use this annotation.
+ */
+@Target({ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface StringTestParameter {
+ String[] value();
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/TestTag.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/TestTag.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/TestTag.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/TestTag.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/TestTags.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/TestTags.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/TestTags.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/TestTags.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/CanSetPolicyTest.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/CanSetPolicyTest.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/CanSetPolicyTest.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/CanSetPolicyTest.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/CannotSetPolicyTest.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/CannotSetPolicyTest.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/CannotSetPolicyTest.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/CannotSetPolicyTest.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDelegate.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDelegate.java
new file mode 100644
index 0000000..89b4c8f
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDelegate.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.enterprise;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
+
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test requires that the given admin delegates the given scope to a test app.
+ *
+ * <p>You should use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method. You can use {@code Devicestate#delegate()} to interact with
+ * the delegate.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsureHasDelegate {
+
+ int ENSURE_HAS_DELEGATE_WEIGHT = DO_PO_WEIGHT + 1; // Should run after setting DO/PO
+
+ enum AdminType {
+ DEVICE_OWNER,
+ PROFILE_OWNER,
+ PRIMARY
+ }
+
+ /**
+ * The admin that should delegate this scope.
+ *
+ * <p>If this is set to {@link AdminType#PRIMARY} and {@link #isPrimary()} is true, then the
+ * delegate will replace the primary dpc as primary without error.
+ */
+ AdminType admin();
+
+ /** The scope being delegated. */
+ String[] scopes();
+
+ /**
+ * Whether this delegate should be returned by calls to {@code Devicestate#policyManager()}.
+ *
+ * <p>Only one policy manager per test should be marked as primary.
+ */
+ boolean isPrimary() default false;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default ENSURE_HAS_DELEGATE_WEIGHT;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDeviceOwner.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDeviceOwner.java
new file mode 100644
index 0000000..2626900c
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDeviceOwner.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.enterprise;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+import static com.android.bedstead.nene.packages.CommonPackages.FEATURE_DEVICE_ADMIN;
+
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+import com.android.bedstead.harrier.annotations.FailureMode;
+import com.android.bedstead.harrier.annotations.RequireFeature;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test requires that a device owner is available on the device.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a device which has
+ * a device owner. Otherwise, you can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method. If using {@code Devicestate}, you can use
+ * {@code Devicestate#deviceOwner()} to interact with the device owner.
+ *
+ * <p>When running on a device with a headless system user, enforcing this with {@code Devicestate}
+ * will also result in the profile owner of the current user being set to the same device policy
+ * controller.
+ *
+ * <p>If {@code Devicestate} is required to set the device owner (because there isn't one already)
+ * then all users and accounts may be removed from the device.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequireFeature(FEATURE_DEVICE_ADMIN)
+public @interface EnsureHasDeviceOwner {
+
+ int DO_PO_WEIGHT = MIDDLE;
+
+ /** Behaviour if the device owner cannot be set. */
+ FailureMode failureMode() default FailureMode.FAIL;
+
+ /**
+ * Whether this DPC should be returned by calls to {@code Devicestate#dpc()} or
+ * {@code Devicestate#policyManager()}}.
+ *
+ * <p>Only one policy manager per test should be marked as primary.
+ */
+ boolean isPrimary() default false;
+
+ /**
+ * Affiliation ids to be set for the device owner.
+ */
+ String[] affiliationIds() default {};
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default DO_PO_WEIGHT;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDelegate.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDelegate.java
new file mode 100644
index 0000000..224d89a
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDelegate.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.enterprise;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDelegate.ENSURE_HAS_DELEGATE_WEIGHT;
+
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test requires that the given admin does not delegate the given scope to a test app.
+ *
+ * <p>You should use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsureHasNoDelegate {
+
+ enum AdminType {
+ DEVICE_OWNER,
+ PROFILE_OWNER,
+ PRIMARY,
+ ANY
+ }
+
+
+ /**
+ * The admin that should not delegate this scope.
+ *
+ * <p>Defaults to any admin
+ *
+ * <p>If this is set to {@link AdminType#PRIMARY} and {@link #isPrimary()} is true, then the
+ * delegate will replace the primary dpc as primary without error.
+ */
+ AdminType admin() default AdminType.ANY;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default ENSURE_HAS_DELEGATE_WEIGHT + 1; // Should run after setting delegate
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDeviceOwner.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDeviceOwner.java
new file mode 100644
index 0000000..d7f1598
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDeviceOwner.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.enterprise;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
+
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test requires that there is no device owner on the device.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a device which has
+ * no device owner. Otherwise, you can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsureHasNoDeviceOwner {
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default DO_PO_WEIGHT;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDpc.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDpc.java
new file mode 100644
index 0000000..c222c75
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDpc.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.enterprise;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
+
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test requires that there is no dpc on the device.
+ *
+ * <p>This checks that there is no device owner, the current user has no work profiles, and the
+ * current user has no profile owner.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a device which has
+ * no dpc. Otherwise, you can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@EnsureHasNoDeviceOwner
+@EnsureHasNoWorkProfile
+@EnsureHasNoProfileOwner
+public @interface EnsureHasNoDpc {
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default DO_PO_WEIGHT;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoProfileOwner.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoProfileOwner.java
new file mode 100644
index 0000000..e4e964b
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoProfileOwner.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.enterprise;
+
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
+
+import com.android.bedstead.harrier.UserType;
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test requires that there is no profile owner on the given user.
+ *
+ * <p>You can use {@code Devicestate} to ensure that the device enters the correct state for the
+ * method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsureHasNoProfileOwner {
+ /** Which user type the profile owner should not be attached to. */
+ UserType onUser() default CURRENT_USER;
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default DO_PO_WEIGHT;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasProfileOwner.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasProfileOwner.java
new file mode 100644
index 0000000..4950290
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasProfileOwner.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.enterprise;
+
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
+import static com.android.bedstead.nene.packages.CommonPackages.FEATURE_DEVICE_ADMIN;
+
+import com.android.bedstead.harrier.UserType;
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+import com.android.bedstead.harrier.annotations.RequireFeature;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test requires that a profile owner is set.
+ *
+ * <p>You can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method. If using {@code Devicestate}, you can use
+ * {@code Devicestate#profileOwner()} to interact with the profile owner.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequireFeature(FEATURE_DEVICE_ADMIN)
+public @interface EnsureHasProfileOwner {
+ /** Which user type the work profile should be attached to. */
+ UserType onUser() default CURRENT_USER;
+
+ /**
+ * Whether this DPC should be returned by calls to {@code Devicestate#dpc()} or
+ * {@code Devicestate#policyManager()}}.
+ *
+ * <p>Only one policy manager per test should be marked as primary.
+ */
+ boolean isPrimary() default false;
+
+ /**
+ * If true, uses the {@code DevicePolicyManager#getParentProfileInstance(ComponentName)}
+ * instance of the dpc when calling to .dpc()
+ *
+ * <p>Only used if {@link #isPrimary()} is true.
+ */
+ boolean useParentInstance() default false;
+
+ /**
+ * Affiliation ids to be set for the profile owner.
+ */
+ String[] affiliationIds() default {};
+
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default DO_PO_WEIGHT;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnterprisePolicy.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnterprisePolicy.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnterprisePolicy.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnterprisePolicy.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/NegativePolicyTest.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/NegativePolicyTest.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/NegativePolicyTest.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/NegativePolicyTest.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/PositivePolicyTest.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/PositivePolicyTest.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/PositivePolicyTest.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/PositivePolicyTest.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfile.java
new file mode 100644
index 0000000..75f610d
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfile.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.meta;
+
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.Target;
+
+/**
+ * This annotation should not be used directly. It exists as a template for annotations which
+ * ensure that a given user does not have a specific profile type.
+ */
+@Target({})
+public @interface EnsureHasNoProfile {
+ UserType forUser() default CURRENT_USER;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfileAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfileAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfileAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfileAnnotation.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoUser.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoUserAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoUserAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoUserAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoUserAnnotation.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfile.java
new file mode 100644
index 0000000..31740ca
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfile.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.meta;
+
+import static com.android.bedstead.harrier.OptionalBoolean.ANY;
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.Target;
+
+/**
+ * This annotation should not be used directly. It exists as a template for annotations which
+ * ensure that a given user has a specific profile type.
+ */
+@Target({})
+public @interface EnsureHasProfile {
+ /** Which user type the profile should be attached to. */
+ UserType forUser() default CURRENT_USER;
+
+ /** Whether the test app should be installed in the profile. */
+ boolean installTestApp() default true;
+
+ /**
+ * Whether the profile owner's DPC should be returned by calls to {@code Devicestate#dpc()}.
+ *
+ * <p>Only one device policy controller per test should be marked as primary.
+ */
+ // NOTE: This field is only required if hasProfileOwner=true
+ boolean dpcIsPrimary() default false;
+
+ /**
+ * Should we ensure that we are switched to the parent of the profile.
+ */
+ OptionalBoolean switchedToParentUser() default ANY;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfileAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfileAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfileAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfileAnnotation.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasUser.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasUserAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasUserAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasUserAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasUserAnnotation.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/ParameterizedAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/ParameterizedAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/ParameterizedAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/ParameterizedAnnotation.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RepeatingAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RepeatingAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RepeatingAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RepeatingAnnotation.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfile.java
new file mode 100644
index 0000000..ce10411
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfile.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.meta;
+
+import static com.android.bedstead.harrier.OptionalBoolean.ANY;
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+
+import java.lang.annotation.Target;
+
+/**
+ * This annotation should not be used directly. It exists as a template for annotations which
+ * ensure that a test runs on a given profile type.
+ */
+@Target({})
+public @interface RequireRunOnProfile {
+ OptionalBoolean installInstrumentedAppInParent() default ANY;
+
+ /**
+ * Whether the profile owner's DPC should be returned by calls to {@code Devicestate#dpc()}.
+ *
+ * <p>Only one device policy controller per test should be marked as primary.
+ */
+ // NOTE: This field is only required if hasProfileOwner=true
+ boolean dpcIsPrimary() default false;
+
+ /**
+ * Affiliation ids to be set for the profile owner.
+ */
+ // NOTE: This field is only required if hasProfileOwner=true
+ String[] affiliationIds() default {};
+
+ /**
+ * Should we ensure that we are switched to the parent of the profile.
+ */
+ OptionalBoolean switchedToParentUser() default TRUE;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfileAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfileAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfileAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfileAnnotation.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnUser.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnUserAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnUserAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnUserAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnUserAnnotation.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequiresBedsteadJUnit4.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequiresBedsteadJUnit4.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequiresBedsteadJUnit4.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequiresBedsteadJUnit4.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeNone.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeNone.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeNone.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeNone.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnAffiliatedDeviceOwnerSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnAffiliatedDeviceOwnerSecondaryUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnAffiliatedDeviceOwnerSecondaryUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnAffiliatedDeviceOwnerSecondaryUser.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnAffiliatedProfileOwnerSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnAffiliatedProfileOwnerSecondaryUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnAffiliatedProfileOwnerSecondaryUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnAffiliatedProfileOwnerSecondaryUser.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnBackgroundDeviceOwnerUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnBackgroundDeviceOwnerUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnBackgroundDeviceOwnerUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnBackgroundDeviceOwnerUser.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnDeviceOwnerUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnDeviceOwnerUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnDeviceOwnerUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnDeviceOwnerUser.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfCorporateOwnedProfileOwner.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfCorporateOwnedProfileOwner.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfCorporateOwnedProfileOwner.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfCorporateOwnedProfileOwner.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfProfileOwnerUsingParentInstance.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfProfileOwnerUsingParentInstance.java
new file mode 100644
index 0000000..d70b32a
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfProfileOwnerUsingParentInstance.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.parameterized;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.LATE;
+
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner;
+import com.android.bedstead.harrier.annotations.meta.ParameterizedAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Parameterize a test so that it runs on the parent of a profile owner and .dpc() relates to the
+ * parent instance of the dpc in the profile.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@ParameterizedAnnotation
+@RequireRunOnPrimaryUser
+@EnsureHasNoDeviceOwner
+@EnsureHasWorkProfile(dpcIsPrimary = true, useParentInstanceOfDpc = true)
+public @interface IncludeRunOnParentOfProfileOwnerUsingParentInstance {
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default LATE;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnPrimaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnPrimaryUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnPrimaryUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnPrimaryUser.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerPrimaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerPrimaryUser.java
new file mode 100644
index 0000000..96c73ab
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerPrimaryUser.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.parameterized;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.LATE;
+
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasProfileOwner;
+import com.android.bedstead.harrier.annotations.meta.ParameterizedAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Parameterize a test so that it runs on the primary user
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@ParameterizedAnnotation
+// Explicitly primary user which excludes headless users as this isn't a valid mode on headless
+@RequireRunOnPrimaryUser
+@EnsureHasNoDeviceOwner
+@EnsureHasProfileOwner(isPrimary = true)
+public @interface IncludeRunOnProfileOwnerPrimaryUser {
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default LATE;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerProfileWithNoDeviceOwner.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerProfileWithNoDeviceOwner.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerProfileWithNoDeviceOwner.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerProfileWithNoDeviceOwner.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUser.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile.java
new file mode 100644
index 0000000..a243839
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.parameterized;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.LATE;
+
+import com.android.bedstead.harrier.UserType;
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser;
+import com.android.bedstead.harrier.annotations.meta.ParameterizedAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Parameterize a test so that it runs on a device which has a primary and work profile, but runs
+ * the test on a secondary user which is in a different profile group.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@ParameterizedAnnotation
+@RequireRunOnSecondaryUser
+@EnsureHasWorkProfile(forUser = UserType.PRIMARY_USER, dpcIsPrimary = true)
+public @interface IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile {
+ /**
+ * Weight sets the order that annotations will be resolved.
+ *
+ * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+ *
+ * <p>If there is an order requirement between annotations, ensure that the weight of the
+ * annotation which must be resolved first is lower than the one which must be resolved later.
+ *
+ * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+ */
+ int weight() default LATE;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnUnaffiliatedProfileOwnerSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnUnaffiliatedProfileOwnerSecondaryUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnUnaffiliatedProfileOwnerSecondaryUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnUnaffiliatedProfileOwnerSecondaryUser.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/AccountManagement.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/AccountManagement.java
new file mode 100644
index 0000000..833fdc9
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/AccountManagement.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for account management tests.
+ *
+ * <p>This is used by {@code
+ * DevicePolicyManager#setAccountManagementDisabled(ComponentName, String, boolean)} and
+ * {@code DevicePolicyManager#getAccountTypesWithManagementDisabled()}.
+ */
+@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER)
+public final class AccountManagement {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictions.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictions.java
new file mode 100644
index 0000000..19b7f78
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictions.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_IN_BACKGROUND;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.CAN_BE_DELEGATED;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_APP_RESTRICTIONS;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for application restrictions.
+ *
+ * <p>This is used by methods such as
+ * {@code DevicePolicyManager#setApplicationRestrictions(ComponentName, String, Bundle)} and
+ * {@code DevicePolicyManager#getApplicationRestrictions(ComponentName, String)}.
+ */
+@EnterprisePolicy(
+ dpc = {
+ APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIES_IN_BACKGROUND | CAN_BE_DELEGATED,
+ APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED},
+ delegatedScopes = DELEGATION_APP_RESTRICTIONS
+ )
+public final class ApplicationRestrictions {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictionsManagingPackage.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictionsManagingPackage.java
new file mode 100644
index 0000000..b41ac24
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictionsManagingPackage.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_IN_BACKGROUND;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for application restrictions.
+ *
+ * <p>This is used by the method
+ * {@code DevicePolicyManager#setApplicationRestrictionsManagingPackage(ComponentName, String, Bundle)}
+ */
+@EnterprisePolicy(
+ dpc = {
+ APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIES_IN_BACKGROUND,
+ APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
+public final class ApplicationRestrictionsManagingPackage {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CaCertManagement.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CaCertManagement.java
new file mode 100644
index 0000000..1cd71e2
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CaCertManagement.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policies around installing/uninstalling CaCerts
+ *
+ * <p>This is used by methods such as
+ * {@code DevicePolicyManager#installCaCert(ComponentName, byte[])} and
+ * {@code DevicePolicyManager#uninstallCaCert(ComponentName, byte[])}.
+ */
+@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
+public final class CaCertManagement {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CameraPolicy.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CameraPolicy.java
new file mode 100644
index 0000000..5e2eeff
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CameraPolicy.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_PROFILE;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_GLOBALLY;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for application restrictions.
+ *
+ * <p>This is used by methods such as
+ * {@code DevicePolicyManager#setCameraDisabled(ComponentName, boolean)} and
+ * {@code DevicePolicyManager#getCameraDisabled(ComponentName)}.
+ */
+// TODO(b/201753989): Update the profileOwner flag once the behaviour of setCameraDisabled
+// is properly defined on secondary user POs.
+@EnterprisePolicy(dpc = {
+ APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO | APPLIES_GLOBALLY,
+ APPLIED_BY_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER
+})
+public class CameraPolicy {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CreateAndManageUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CreateAndManageUser.java
new file mode 100644
index 0000000..098a20d
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CreateAndManageUser.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for create and manage user.
+ *
+ * <p>This is used by methods such as {@code DevicePolicyManager#createAndManageUser(
+ * ComponentName, String, ComponentName, PersistableBundle, int)}.
+ */
+@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER})
+public final class CreateAndManageUser {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DefaultSmsApplication.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DefaultSmsApplication.java
new file mode 100644
index 0000000..2f1494f
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DefaultSmsApplication.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for set default SMS application test.
+ *
+ * <p>This is used by {@code DevicePolicyManager#setDefaultSmsApplication(ComponentName, String)}.
+ */
+@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER)
+public final class DefaultSmsApplication {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/Delegation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/Delegation.java
new file mode 100644
index 0000000..4512f48
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/Delegation.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for general admin delegation that doesn't have additional scope-specific constraints on
+ * the admin type. Specific delegations with these constraints have their own policies.
+ *
+ * <p>This is used for methods such as {@code
+ * DevicePolicyManager#setDelegatedScopes(ComponentName, String, List)}.
+ */
+@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
+public final class Delegation {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DeprecatedResetPassword.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DeprecatedResetPassword.java
new file mode 100644
index 0000000..a3fc08a
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DeprecatedResetPassword.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for {@code DevicePolicyManager#resetPassword(String, int)}.
+ */
+@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
+public final class DeprecatedResetPassword {
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/DisallowNetworkReset.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DisallowNetworkReset.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/DisallowNetworkReset.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DisallowNetworkReset.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/DisallowPrivateDnsConfig.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DisallowPrivateDnsConfig.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/DisallowPrivateDnsConfig.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DisallowPrivateDnsConfig.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/EnrollmentSpecificId.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/EnrollmentSpecificId.java
new file mode 100644
index 0000000..c054f49
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/EnrollmentSpecificId.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for testing enrollment specific ID.
+ * See {@code DevicePolicyManager#getEnrollmentSpecificId()} for more detail.
+ */
+@EnterprisePolicy(dpc = {
+ APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER,
+ APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER
+})
+public final class EnrollmentSpecificId {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/KeyManagement.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/KeyManagement.java
new file mode 100644
index 0000000..ef9fb68
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/KeyManagement.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policies around key management
+ *
+ * <p>This is used by methods such as
+ * {@code DevicePolicyManager#installKeyPair(ComponentName, PrivateKey, Certificate, String)} and
+ * {@code DevicePolicyManager#removeKeyPair(ComponentName, String)}.
+ */
+@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER,
+ APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
+public final class KeyManagement {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/LockNow.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/LockNow.java
new file mode 100644
index 0000000..823cf8e
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/LockNow.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PARENT_INSTANCE_OF_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+import static com.android.bedstead.nene.permissions.CommonPermissions.LOCK_DEVICE;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for {@code DevicePolicyManager#lockNow()}.
+ */
+// This is not applied by profile owner as the behaviour is different with a unified challenge
+@EnterprisePolicy(dpc =
+ APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PARENT_INSTANCE_OF_PROFILE_OWNER | APPLIES_TO_OWN_USER,
+ permissions =
+ @EnterprisePolicy.Permission(appliedWith = LOCK_DEVICE, appliesTo = APPLIES_TO_OWN_USER))
+public final class LockNow {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/LockTask.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/LockTask.java
new file mode 100644
index 0000000..057824c
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/LockTask.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_AFFILIATED_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policies around Lock Task mode
+ * (https://developer.android.com/work/dpc/dedicated-devices/lock-task-mode).
+ *
+ * <p>This is used by methods such as
+ * {@code DevicePolicyManager#setLockTaskFeatures(ComponentName, int)} and
+ * {@code DevicePolicyManager#setLockTaskPackages(ComponentName, String[])}.
+ */
+@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER,
+ APPLIED_BY_AFFILIATED_PROFILE_OWNER | APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO | APPLIES_TO_OWN_USER})
+public final class LockTask {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/NetworkLoggingDelegation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/NetworkLoggingDelegation.java
new file mode 100644
index 0000000..8b5b2bd
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/NetworkLoggingDelegation.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_PROFILE;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for network logging delegation.
+ *
+ * <p>This is used for methods such as {@code
+ * DevicePolicyManager#setDelegatedScopes(ComponentName, String, List)} with scope {@code
+ * DevicePolicyManager#DELEGATION_NETWORK_LOGGING}.
+ */
+@EnterprisePolicy(dpc = {
+ APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER})
+public final class NetworkLoggingDelegation {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/PreferentialNetworkService.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/PreferentialNetworkService.java
new file mode 100644
index 0000000..82603be
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/PreferentialNetworkService.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_PROFILE;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for testing preferential network service.
+ * See {@code DevicePolicyManager#setPreferentialNetworkServiceEnabled(boolean)} for more detail.
+ */
+@EnterprisePolicy(dpc = APPLIED_BY_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER)
+public final class PreferentialNetworkService {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ResetPasswordWithToken.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ResetPasswordWithToken.java
new file mode 100644
index 0000000..0d1a832
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ResetPasswordWithToken.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policies around resetting a new device password
+ *
+ * <p>This is used by methods such as
+ * {@code DevicePolicyManager#resetPasswordWithToken(ComponentName, String, byte[], int)}
+ */
+@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER,
+ APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
+public class ResetPasswordWithToken {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java
new file mode 100644
index 0000000..8cdb1b0
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PARENT_INSTANCE_OF_COPE_PROFILE_OWNER_PROFILE;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for disabling screen capture.
+ *
+ * <p>Users of this policy are
+ * {@code DevicePolicyManager#setScreenCaptureDisabled(ComponentName, boolean)}.
+ */
+@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER
+ | APPLIED_BY_PARENT_INSTANCE_OF_COPE_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER)
+public final class ScreenCaptureDisabled {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SecurityLoggingDelegation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SecurityLoggingDelegation.java
new file mode 100644
index 0000000..7dfcf6f
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SecurityLoggingDelegation.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for security logging delegation.
+ *
+ * <p>This is used for methods such as {@code
+ * DevicePolicyManager#setDelegatedScopes(ComponentName, String, List)} with scope {@code
+ * DevicePolicyManager#DELEGATION_SECURITY_LOGGING}.
+ */
+// TODO(b/198774281): COPE profile POs can call this too, but we need to add the flag.
+@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER})
+public final class SecurityLoggingDelegation {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetPermissionGrantState.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetPermissionGrantState.java
new file mode 100644
index 0000000..7fe01ac
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetPermissionGrantState.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.CAN_BE_DELEGATED;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_PERMISSION_GRANT;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policies around setting the grant state of a basic permission.
+ *
+ * <p>This is used by
+ * {@code DevicePolicyManager#setPermissionGrantState(ComponentName, String, String, int)} when
+ * granting permissions not covered by other policies.
+ */
+@EnterprisePolicy(
+ dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED,
+ delegatedScopes = DELEGATION_PERMISSION_GRANT)
+public final class SetPermissionGrantState {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetSensorPermissionGranted.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetSensorPermissionGranted.java
new file mode 100644
index 0000000..b69851e
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetSensorPermissionGranted.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policies around setting the grant state of a sensor permission.
+ *
+ * <p>This is used by
+ * {@code DevicePolicyManager#setPermissionGrantState(ComponentName, String, String, int)} when
+ * granting sensor permissions.
+ */
+@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER)
+public final class SetSensorPermissionGranted {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetSmsPermissionGranted.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetSmsPermissionGranted.java
new file mode 100644
index 0000000..b4d2a03
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetSmsPermissionGranted.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.CAN_BE_DELEGATED;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_PERMISSION_GRANT;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policies around setting the grant state of a SMS related permission.
+ *
+ * <p>This is used by
+ * {@code DevicePolicyManager#setPermissionGrantState(ComponentName, String, String, int)} when
+ * granting sms-related permissions.
+ */
+// TODO(198311372): Check if APPLIED_BY_PROFILE_OWNER_USER is expected
+@EnterprisePolicy(
+ dpc = APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIED_BY_PROFILE_OWNER_USER | CAN_BE_DELEGATED,
+ delegatedScopes = DELEGATION_PERMISSION_GRANT)
+public final class SetSmsPermissionGranted {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SupportMessage.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SupportMessage.java
new file mode 100644
index 0000000..7f16f55
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SupportMessage.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for long and short support messages.
+ *
+ * <p>Users of this policy are {@code DevicePolicyManager#setLongSupportMessage(ComponentName,
+ * CharSequence)}, {@code DevicePolicyManager#setShortSupportMessage(ComponentName, CharSequence)},
+ * {@code DevicePolicyManager#getLongSupportMessage(ComponentName)} and {@code
+ * DevicePolicyManager#getShortSupportMessage(ComponentName)}.
+ */
+@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER)
+public final class SupportMessage {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/UserControlDisabledPackages.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/UserControlDisabledPackages.java
new file mode 100644
index 0000000..c47db87
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/UserControlDisabledPackages.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_GLOBALLY;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for user control disabled packages.
+ *
+ * <p>This is used by methods such as
+ * {@code DevicePolicyManager#setUserControlDisabledPackages(ComponentName, List)} and
+ * {@code DevicePolicyManager#getUserControlDisabledPackages(ComponentName)}.
+ */
+@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIES_GLOBALLY)
+public final class UserControlDisabledPackages {
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/BedsteadJUnit4.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/BedsteadJUnit4.java
deleted file mode 100644
index 7ffabbe..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/BedsteadJUnit4.java
+++ /dev/null
@@ -1,489 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier;
-
-import android.os.Bundle;
-
-import androidx.annotation.Nullable;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-import com.android.bedstead.harrier.annotations.enterprise.CanSetPolicyTest;
-import com.android.bedstead.harrier.annotations.enterprise.CannotSetPolicyTest;
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-import com.android.bedstead.harrier.annotations.enterprise.NegativePolicyTest;
-import com.android.bedstead.harrier.annotations.enterprise.PositivePolicyTest;
-import com.android.bedstead.harrier.annotations.meta.ParameterizedAnnotation;
-import com.android.bedstead.harrier.annotations.meta.RepeatingAnnotation;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeNone;
-import com.android.bedstead.nene.exceptions.NeneException;
-
-import com.google.common.base.Objects;
-
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runners.BlockJUnit4ClassRunner;
-import org.junit.runners.model.FrameworkMethod;
-import org.junit.runners.model.InitializationError;
-import org.junit.runners.model.TestClass;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * A JUnit test runner for use with Bedstead.
- */
-public final class BedsteadJUnit4 extends BlockJUnit4ClassRunner {
-
- private static final String BEDSTEAD_PACKAGE_NAME = "com.android.bedstead";
-
- // These are annotations which are not included indirectly
- private static final Set<String> sIgnoredAnnotationPackages = new HashSet<>();
- static {
- sIgnoredAnnotationPackages.add("java.lang.annotation");
- sIgnoredAnnotationPackages.add("com.android.bedstead.harrier.annotations.meta");
- sIgnoredAnnotationPackages.add("kotlin.*");
- sIgnoredAnnotationPackages.add("org.junit");
- }
-
- private static int annotationSorter(Annotation a, Annotation b) {
- return getAnnotationWeight(a) - getAnnotationWeight(b);
- }
-
- private static int getAnnotationWeight(Annotation annotation) {
- if (annotation instanceof DynamicParameterizedAnnotation) {
- // Special case, not important
- return AnnotationRunPrecedence.PRECEDENCE_NOT_IMPORTANT;
- }
-
- if (!annotation.annotationType().getPackage().getName().startsWith(BEDSTEAD_PACKAGE_NAME)) {
- return AnnotationRunPrecedence.FIRST;
- }
-
- try {
- return (int) annotation.annotationType().getMethod("weight").invoke(annotation);
- } catch (NoSuchMethodException e) {
- // Default to PRECEDENCE_NOT_IMPORTANT if no weight is found on the annotation.
- return AnnotationRunPrecedence.PRECEDENCE_NOT_IMPORTANT;
- } catch (IllegalAccessException | InvocationTargetException e) {
- throw new NeneException("Failed to invoke weight on this annotation: " + annotation, e);
- }
- }
-
- /**
- * {@link FrameworkMethod} subclass which allows modifying the test name and annotations.
- */
- public static final class BedsteadFrameworkMethod extends FrameworkMethod {
-
- private final Annotation mParameterizedAnnotation;
- private final Map<Class<? extends Annotation>, Annotation> mAnnotationsMap =
- new HashMap<>();
- private Annotation[] mAnnotations;
-
- public BedsteadFrameworkMethod(Method method) {
- this(method, /* parameterizedAnnotation= */ null);
- }
-
- public BedsteadFrameworkMethod(Method method, Annotation parameterizedAnnotation) {
- super(method);
- mParameterizedAnnotation = parameterizedAnnotation;
-
- calculateAnnotations();
- }
-
- private void calculateAnnotations() {
- List<Annotation> annotations =
- new ArrayList<>(Arrays.asList(getDeclaringClass().getAnnotations()));
- annotations.sort(BedsteadJUnit4::annotationSorter);
-
- annotations.addAll(Arrays.stream(getMethod().getAnnotations())
- .sorted(BedsteadJUnit4::annotationSorter)
- .collect(Collectors.toList()));
-
- parseEnterpriseAnnotations(annotations);
-
- resolveRecursiveAnnotations(annotations, mParameterizedAnnotation);
-
- this.mAnnotations = annotations.toArray(new Annotation[0]);
- for (Annotation annotation : annotations) {
- if (annotation instanceof DynamicParameterizedAnnotation) {
- continue; // don't return this
- }
- mAnnotationsMap.put(annotation.annotationType(), annotation);
- }
- }
-
- @Override
- public String getName() {
- if (mParameterizedAnnotation == null) {
- return super.getName();
- }
- return super.getName() + "[" + getParameterName(mParameterizedAnnotation) + "]";
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!super.equals(obj)) {
- return false;
- }
-
- if (!(obj instanceof BedsteadFrameworkMethod)) {
- return false;
- }
-
- BedsteadFrameworkMethod other = (BedsteadFrameworkMethod) obj;
-
- return Objects.equal(mParameterizedAnnotation, other.mParameterizedAnnotation);
- }
-
- @Override
- public Annotation[] getAnnotations() {
- return mAnnotations;
- }
-
- @Override
- public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
- return (T) mAnnotationsMap.get(annotationType);
- }
- }
-
- private static String getParameterName(Annotation annotation) {
- if (annotation instanceof DynamicParameterizedAnnotation) {
- return ((DynamicParameterizedAnnotation) annotation).name();
- }
- return annotation.annotationType().getSimpleName();
- }
-
- /**
- * Resolve annotations recursively.
- *
- * @param parameterizedAnnotation The class of the parameterized annotation to expand, if any
- */
- public static void resolveRecursiveAnnotations(List<Annotation> annotations,
- @Nullable Annotation parameterizedAnnotation) {
- int index = 0;
- while (index < annotations.size()) {
- Annotation annotation = annotations.get(index);
- annotations.remove(index);
- List<Annotation> replacementAnnotations =
- getReplacementAnnotations(annotation, parameterizedAnnotation);
- replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
- annotations.addAll(index, replacementAnnotations);
- index += replacementAnnotations.size();
- }
- }
-
- private static boolean isParameterizedAnnotation(Annotation annotation) {
- if (annotation instanceof DynamicParameterizedAnnotation) {
- return true;
- }
-
- return annotation.annotationType().getAnnotation(ParameterizedAnnotation.class) != null;
- }
-
- private static Annotation[] getIndirectAnnotations(Annotation annotation) {
- if (annotation instanceof DynamicParameterizedAnnotation) {
- return ((DynamicParameterizedAnnotation) annotation).annotations();
- }
- return annotation.annotationType().getAnnotations();
- }
-
- private static boolean isRepeatingAnnotation(Annotation annotation) {
- if (annotation instanceof DynamicParameterizedAnnotation) {
- return false;
- }
-
- return annotation.annotationType().getAnnotation(RepeatingAnnotation.class) != null;
- }
-
- private static List<Annotation> getReplacementAnnotations(Annotation annotation,
- @Nullable Annotation parameterizedAnnotation) {
- List<Annotation> replacementAnnotations = new ArrayList<>();
-
- if (isRepeatingAnnotation(annotation)) {
- try {
- Annotation[] annotations =
- (Annotation[]) annotation.annotationType()
- .getMethod("value").invoke(annotation);
- Collections.addAll(replacementAnnotations, annotations);
- return replacementAnnotations;
- } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
- throw new NeneException("Error expanding repeated annotations", e);
- }
- }
-
- if (isParameterizedAnnotation(annotation) && !annotation.equals(parameterizedAnnotation)) {
- return replacementAnnotations;
- }
-
- for (Annotation indirectAnnotation : getIndirectAnnotations(annotation)) {
- if (shouldSkipAnnotation(annotation)) {
- continue;
- }
-
- replacementAnnotations.addAll(getReplacementAnnotations(
- indirectAnnotation, parameterizedAnnotation));
- }
-
- if (!(annotation instanceof DynamicParameterizedAnnotation)) {
- // We drop the fake annotation once it's replaced
- replacementAnnotations.add(annotation);
- }
-
- return replacementAnnotations;
- }
-
- private static boolean shouldSkipAnnotation(Annotation annotation) {
- if (annotation instanceof DynamicParameterizedAnnotation) {
- return false;
- }
-
- String annotationPackage = annotation.annotationType().getPackage().getName();
-
- for (String ignoredPackage : sIgnoredAnnotationPackages) {
- if (ignoredPackage.endsWith(".*")) {
- if (annotationPackage.startsWith(
- ignoredPackage.substring(0, ignoredPackage.length() - 2))) {
- return true;
- }
- } else if (annotationPackage.equals(ignoredPackage)) {
- return true;
- }
- }
-
- return false;
- }
-
- public BedsteadJUnit4(Class<?> testClass) throws InitializationError {
- super(testClass);
- }
-
- private boolean annotationShouldBeSkipped(Annotation annotation) {
- if (annotation instanceof DynamicParameterizedAnnotation) {
- return false;
- }
-
- return annotation.annotationType().equals(IncludeNone.class);
- }
-
- @Override
- protected List<FrameworkMethod> computeTestMethods() {
- TestClass testClass = getTestClass();
-
- List<FrameworkMethod> basicTests = testClass.getAnnotatedMethods(Test.class);
- List<FrameworkMethod> modifiedTests = new ArrayList<>();
-
- for (FrameworkMethod m : basicTests) {
- Set<Annotation> parameterizedAnnotations = getParameterizedAnnotations(m);
-
- if (parameterizedAnnotations.isEmpty()) {
- // Unparameterized, just add the original
- modifiedTests.add(new BedsteadFrameworkMethod(m.getMethod()));
- }
-
- for (Annotation annotation : parameterizedAnnotations) {
- if (annotationShouldBeSkipped(annotation)) {
- // Special case - does not generate a run
- continue;
- }
- modifiedTests.add(
- new BedsteadFrameworkMethod(m.getMethod(), annotation));
- }
- }
-
- sortMethodsByBedsteadAnnotations(modifiedTests);
-
- return modifiedTests;
- }
-
- /**
- * Sort methods so that methods with identical bedstead annotations are together.
- *
- * <p>This will also ensure that all tests methods which are not annotated for bedstead will
- * run before any tests which are annotated.
- */
- private void sortMethodsByBedsteadAnnotations(List<FrameworkMethod> modifiedTests) {
- List<Annotation> bedsteadAnnotationsSortedByMostCommon =
- bedsteadAnnotationsSortedByMostCommon(modifiedTests);
-
- modifiedTests.sort((o1, o2) -> {
- for (Annotation annotation : bedsteadAnnotationsSortedByMostCommon) {
- boolean o1HasAnnotation = o1.getAnnotation(annotation.annotationType()) != null;
- boolean o2HasAnnotation = o2.getAnnotation(annotation.annotationType()) != null;
-
- if (o1HasAnnotation && !o2HasAnnotation) {
- // o1 goes to the end
- return 1;
- } else if (o2HasAnnotation && !o1HasAnnotation) {
- return -1;
- }
- }
- return 0;
- });
- }
-
- private List<Annotation> bedsteadAnnotationsSortedByMostCommon(List<FrameworkMethod> methods) {
- Map<Annotation, Integer> annotationCounts = countAnnotations(methods);
- List<Annotation> annotations = new ArrayList<>(annotationCounts.keySet());
-
- annotations.removeIf(
- annotation ->
- !annotation.annotationType()
- .getCanonicalName().contains(BEDSTEAD_PACKAGE_NAME));
-
- annotations.sort(Comparator.comparingInt(annotationCounts::get));
- Collections.reverse(annotations);
-
- return annotations;
- }
-
- private Map<Annotation, Integer> countAnnotations(List<FrameworkMethod> methods) {
- Map<Annotation, Integer> annotationCounts = new HashMap<>();
-
- for (FrameworkMethod method : methods) {
- for (Annotation annotation : method.getAnnotations()) {
- annotationCounts.put(
- annotation, annotationCounts.getOrDefault(annotation, 0) + 1);
- }
- }
-
- return annotationCounts;
- }
-
- private Set<Annotation> getParameterizedAnnotations(FrameworkMethod method) {
- Set<Annotation> parameterizedAnnotations = new HashSet<>();
- List<Annotation> annotations = new ArrayList<>(Arrays.asList(method.getAnnotations()));
-
- // TODO(scottjonathan): We're doing this twice... does it matter?
- parseEnterpriseAnnotations(annotations);
-
- for (Annotation annotation : annotations) {
- if (isParameterizedAnnotation(annotation)) {
- parameterizedAnnotations.add(annotation);
- }
- }
-
- return parameterizedAnnotations;
- }
-
- /**
- * Parse enterprise-specific annotations.
- *
- * <p>To be used before general annotation processing.
- */
- private static void parseEnterpriseAnnotations(List<Annotation> annotations) {
- int index = 0;
- while (index < annotations.size()) {
- Annotation annotation = annotations.get(index);
- if (annotation instanceof PositivePolicyTest) {
- annotations.remove(index);
- Class<?> policy = ((PositivePolicyTest) annotation).policy();
-
- EnterprisePolicy enterprisePolicy =
- policy.getAnnotation(EnterprisePolicy.class);
- List<Annotation> replacementAnnotations =
- Policy.positiveStates(policy.getName(), enterprisePolicy);
- replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
-
- annotations.addAll(index, replacementAnnotations);
- index += replacementAnnotations.size();
- } else if (annotation instanceof NegativePolicyTest) {
- annotations.remove(index);
- Class<?> policy = ((NegativePolicyTest) annotation).policy();
-
- EnterprisePolicy enterprisePolicy =
- policy.getAnnotation(EnterprisePolicy.class);
- List<Annotation> replacementAnnotations =
- Policy.negativeStates(policy.getName(), enterprisePolicy);
- replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
-
- annotations.addAll(index, replacementAnnotations);
- index += replacementAnnotations.size();
- } else if (annotation instanceof CannotSetPolicyTest) {
- annotations.remove(index);
- Class<?> policy = ((CannotSetPolicyTest) annotation).policy();
-
- EnterprisePolicy enterprisePolicy =
- policy.getAnnotation(EnterprisePolicy.class);
- List<Annotation> replacementAnnotations =
- Policy.cannotSetPolicyStates(policy.getName(), enterprisePolicy, ((CannotSetPolicyTest) annotation).includeDeviceAdminStates(), ((CannotSetPolicyTest) annotation).includeNonDeviceAdminStates());
- replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
-
- annotations.addAll(index, replacementAnnotations);
- index += replacementAnnotations.size();
- } else if (annotation instanceof CanSetPolicyTest) {
- annotations.remove(index);
- Class<?> policy = ((CanSetPolicyTest) annotation).policy();
- boolean singleTestOnly = ((CanSetPolicyTest) annotation).singleTestOnly();
-
- EnterprisePolicy enterprisePolicy =
- policy.getAnnotation(EnterprisePolicy.class);
- List<Annotation> replacementAnnotations =
- Policy.canSetPolicyStates(
- policy.getName(), enterprisePolicy, singleTestOnly);
- replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
-
- annotations.addAll(index, replacementAnnotations);
- index += replacementAnnotations.size();
- } else {
- index++;
- }
- }
- }
-
- @Override
- protected List<TestRule> classRules() {
- List<TestRule> rules = super.classRules();
-
- for (TestRule rule : rules) {
- if (rule instanceof DeviceState) {
- DeviceState deviceState = (DeviceState) rule;
-
- deviceState.setSkipTestTeardown(true);
- deviceState.setUsingBedsteadJUnit4(true);
-
- break;
- }
- }
-
- return rules;
- }
-
- /**
- * True if the test is running in debug mode.
- *
- * <p>This will result in additional debugging information being added which would otherwise
- * be dropped to improve test performance.
- *
- * <p>To enable this, pass the "bedstead-debug" instrumentation arg as "true"
- */
- public static boolean isDebug() {
- Bundle arguments = InstrumentationRegistry.getArguments();
- return Boolean.parseBoolean(arguments.getString("bedstead-debug", "false"));
- }
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java
index 75cf62b..9d1f576 100644
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java
@@ -20,7 +20,7 @@
import static android.app.ActivityManager.STOP_USER_ON_SWITCH_DEFAULT;
import static android.app.ActivityManager.STOP_USER_ON_SWITCH_FALSE;
-import static com.android.bedstead.nene.permissions.Permissions.NOTIFY_PENDING_SYSTEM_UPDATE;
+import static com.android.bedstead.harrier.Defaults.DEFAULT_PASSWORD;
import static com.android.bedstead.nene.users.UserType.MANAGED_PROFILE_TYPE_NAME;
import static com.android.bedstead.nene.users.UserType.SECONDARY_USER_TYPE_NAME;
import static com.android.bedstead.nene.utils.Versions.meetsSdkVersionRequirements;
@@ -39,6 +39,7 @@
import android.os.Bundle;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -47,10 +48,12 @@
import com.android.bedstead.harrier.annotations.EnsureDoesNotHavePermission;
import com.android.bedstead.harrier.annotations.EnsureHasPermission;
import com.android.bedstead.harrier.annotations.EnsurePackageNotInstalled;
+import com.android.bedstead.harrier.annotations.EnsurePasswordNotSet;
+import com.android.bedstead.harrier.annotations.EnsurePasswordSet;
+import com.android.bedstead.harrier.annotations.EnsureScreenIsOn;
import com.android.bedstead.harrier.annotations.FailureMode;
import com.android.bedstead.harrier.annotations.RequireDoesNotHaveFeature;
import com.android.bedstead.harrier.annotations.RequireFeature;
-import com.android.bedstead.harrier.annotations.RequireGmsInstrumentation;
import com.android.bedstead.harrier.annotations.RequireHeadlessSystemUserMode;
import com.android.bedstead.harrier.annotations.RequireLowRamDevice;
import com.android.bedstead.harrier.annotations.RequireNotHeadlessSystemUserMode;
@@ -58,6 +61,7 @@
import com.android.bedstead.harrier.annotations.RequirePackageInstalled;
import com.android.bedstead.harrier.annotations.RequirePackageNotInstalled;
import com.android.bedstead.harrier.annotations.RequireSdkVersion;
+import com.android.bedstead.harrier.annotations.RequireTargetSdkVersion;
import com.android.bedstead.harrier.annotations.RequireUserSupported;
import com.android.bedstead.harrier.annotations.TestTag;
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDelegate;
@@ -89,6 +93,7 @@
import com.android.bedstead.nene.utils.Versions;
import com.android.bedstead.remotedpc.RemoteDelegate;
import com.android.bedstead.remotedpc.RemoteDpc;
+import com.android.bedstead.remotedpc.RemoteDpcUsingParentInstance;
import com.android.bedstead.remotedpc.RemotePolicyManager;
import com.android.bedstead.testapp.TestApp;
import com.android.bedstead.testapp.TestAppInstance;
@@ -100,7 +105,6 @@
import junit.framework.AssertionFailedError;
import org.junit.AssumptionViolatedException;
-import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
@@ -134,9 +138,8 @@
*
* {@code assumeTrue} will be used, so tests which do not meet preconditions will be skipped.
*/
-public final class DeviceState implements TestRule {
+public final class DeviceState extends HarrierRule {
- private static final String GMS_PKG = "com.google.android.gms";
private static final ComponentName REMOTE_DPC_COMPONENT_NAME = RemoteDpc.DPC_COMPONENT_NAME;
private static final String SWITCHED_TO_USER = "switchedToUser";
@@ -145,12 +148,15 @@
public static final String FOR_USER = "forUser";
public static final String DPC_IS_PRIMARY = "dpcIsPrimary";
public static final String AFFILIATION_IDS = "affiliationIds";
+ private static final String USE_PARENT_INSTANCE_OF_DPC = "useParentInstanceOfDpc";
private final Context mContext = ApplicationProvider.getApplicationContext();
private static final String SKIP_TEST_TEARDOWN_KEY = "skip-test-teardown";
private static final String SKIP_CLASS_TEARDOWN_KEY = "skip-class-teardown";
private static final String SKIP_TESTS_REASON_KEY = "skip-tests-reason";
private static final String MIN_SDK_VERSION_KEY = "min-sdk-version";
+ private static final String PERMISSIONS_INSTRUMENTATION_PACKAGE_KEY =
+ "permission-instrumentation-package";
private boolean mSkipTestTeardown;
private boolean mSkipClassTeardown;
private boolean mSkipTests;
@@ -161,10 +167,13 @@
// The minimum version supported by tests, defaults to current version
private final int mMinSdkVersion;
private int mMinSdkVersionCurrentTest;
+ private @Nullable String mPermissionsInstrumentationPackage;
+ private final Set<String> mPermissionsInstrumentationPackagePermissions = new HashSet<>();
- // Marks if the conditions for requiring running under GMS instrumentation have been set
- // if not - we assume the test should never run under GMS instrumentation
- private boolean mHasRequireGmsInstrumentation = false;
+ // Marks if the conditions for requiring running under permission instrumentation have been set
+ // if not - we assume the test should never run under permission instrumentation
+ // This is only used if a permission instrumentation package is set
+ private boolean mHasRequirePermissionInstrumentation = false;
private static final String TV_PROFILE_TYPE_NAME = "com.android.tv.profile";
@@ -177,12 +186,21 @@
mSkipTestsReason = arguments.getString(SKIP_TESTS_REASON_KEY, "");
mSkipTests = !mSkipTestsReason.isEmpty();
mMinSdkVersion = arguments.getInt(MIN_SDK_VERSION_KEY, Build.VERSION.SDK_INT);
+ mPermissionsInstrumentationPackage =
+ arguments.getString(PERMISSIONS_INSTRUMENTATION_PACKAGE_KEY);
+ if (mPermissionsInstrumentationPackage != null) {
+ mPermissionsInstrumentationPackagePermissions.addAll(
+ TestApis.packages().find(mPermissionsInstrumentationPackage)
+ .requestedPermissions());
+ }
}
+ @Override
void setSkipTestTeardown(boolean skipTestTeardown) {
mSkipTestTeardown = skipTestTeardown;
}
+ @Override
void setUsingBedsteadJUnit4(boolean usingBedsteadJUnit4) {
mUsingBedsteadJUnit4 = usingBedsteadJUnit4;
}
@@ -216,7 +234,7 @@
mMinSdkVersionCurrentTest = mMinSdkVersion;
List<Annotation> annotations = getAnnotations(description);
- permissionContext = applyAnnotations(annotations);
+ permissionContext = applyAnnotations(annotations, /* isTest= */ true);
Log.d(LOG_TAG,
"Finished preparing state for test " + description.getMethodName());
@@ -241,7 +259,7 @@
}};
}
- private PermissionContextImpl applyAnnotations(List<Annotation> annotations)
+ private PermissionContextImpl applyAnnotations(List<Annotation> annotations, boolean isTest)
throws Throwable {
PermissionContextImpl permissionContext = null;
for (Annotation annotation : annotations) {
@@ -268,10 +286,18 @@
.getMethod(INSTALL_INSTRUMENTED_APP).invoke(annotation);
boolean dpcIsPrimary = false;
+ boolean useParentInstance = false;
if (ensureHasProfileAnnotation.hasProfileOwner()) {
dpcIsPrimary = (boolean)
annotation.annotationType()
.getMethod(DPC_IS_PRIMARY).invoke(annotation);
+
+ if (dpcIsPrimary) {
+ useParentInstance = (boolean)
+ annotation.annotationType()
+ .getMethod(USE_PARENT_INSTANCE_OF_DPC).invoke(annotation);
+
+ }
}
OptionalBoolean switchedToParentUser = (OptionalBoolean)
@@ -281,7 +307,7 @@
ensureHasProfile(
ensureHasProfileAnnotation.value(), installInstrumentedApp,
forUser, ensureHasProfileAnnotation.hasProfileOwner(),
- dpcIsPrimary, switchedToParentUser);
+ dpcIsPrimary, useParentInstance, switchedToParentUser);
continue;
}
@@ -348,6 +374,7 @@
requireRunOnProfile(requireRunOnProfileAnnotation.value(),
installInstrumentedAppInParent,
requireRunOnProfileAnnotation.hasProfileOwner(),
+ /* useParentInstance= */ false,
dpcIsPrimary, switchedToParentUser, affiliationIds);
continue;
}
@@ -407,6 +434,7 @@
(EnsureHasProfileOwner) annotation;
ensureHasProfileOwner(ensureHasProfileOwnerAnnotation.onUser(),
ensureHasProfileOwnerAnnotation.isPrimary(),
+ ensureHasProfileOwnerAnnotation.useParentInstance(),
new HashSet<>(Arrays.asList(ensureHasProfileOwnerAnnotation.affiliationIds())));
continue;
}
@@ -427,14 +455,6 @@
continue;
}
- if (annotation instanceof RequireGmsInstrumentation) {
- RequireGmsInstrumentation requireGmsInstrumentationAnnotation =
- (RequireGmsInstrumentation) annotation;
- requireGmsInstrumentation(requireGmsInstrumentationAnnotation.min(),
- requireGmsInstrumentationAnnotation.max());
- continue;
- }
-
if (annotation instanceof RequireLowRamDevice) {
RequireLowRamDevice requireLowRamDeviceAnnotation =
(RequireLowRamDevice) annotation;
@@ -451,6 +471,18 @@
continue;
}
+ if (annotation instanceof RequireTargetSdkVersion) {
+ RequireTargetSdkVersion requireTargetSdkVersionAnnotation =
+ (RequireTargetSdkVersion) annotation;
+
+ requireTargetSdkVersion(
+ requireTargetSdkVersionAnnotation.min(),
+ requireTargetSdkVersionAnnotation.max(),
+ requireTargetSdkVersionAnnotation.failureMode());
+
+ continue;
+ }
+
if (annotation instanceof RequireSdkVersion) {
RequireSdkVersion requireSdkVersionAnnotation =
(RequireSdkVersion) annotation;
@@ -553,14 +585,35 @@
}
continue;
}
+
+ if (annotation instanceof EnsureScreenIsOn) {
+ ensureScreenIsOn();
+ continue;
+ }
+
+ if (annotation instanceof EnsurePasswordSet) {
+ EnsurePasswordSet ensurePasswordSetAnnotation =
+ (EnsurePasswordSet) annotation;
+ ensurePasswordSet(
+ ensurePasswordSetAnnotation.forUser(),
+ ensurePasswordSetAnnotation.password());
+ continue;
+ }
+
+ if (annotation instanceof EnsurePasswordNotSet) {
+ EnsurePasswordNotSet ensurePasswordNotSetAnnotation =
+ (EnsurePasswordNotSet) annotation;
+ ensurePasswordNotSet(ensurePasswordNotSetAnnotation.forUser());
+ continue;
+ }
}
requireSdkVersion(/* min= */ mMinSdkVersionCurrentTest,
/* max= */ Integer.MAX_VALUE, FailureMode.SKIP);
- if (!mHasRequireGmsInstrumentation) {
- // TODO(scottjonathan): Only enforce if we've configured GMS Instrumentation
- requireNoGmsInstrumentation();
+ if (isTest && mPermissionsInstrumentationPackage != null
+ && !mHasRequirePermissionInstrumentation) {
+ requireNoPermissionsInstrumentation();
}
return permissionContext;
@@ -636,7 +689,7 @@
try {
List<Annotation> annotations =
new ArrayList<>(getAnnotations(description));
- permissionContext = applyAnnotations(annotations);
+ permissionContext = applyAnnotations(annotations, /* isTest= */ false);
} catch (AssumptionViolatedException e) {
Log.i(LOG_TAG, "Assumption failed during class setup", e);
mSkipTests = true;
@@ -751,7 +804,7 @@
private void requireRunOnProfile(String userType,
OptionalBoolean installInstrumentedAppInParent,
- boolean hasProfileOwner, boolean dpcIsPrimary,
+ boolean hasProfileOwner, boolean dpcIsPrimary, boolean useParentInstance,
OptionalBoolean switchedToParentUser, Set<String> affiliationIds) {
UserReference instrumentedUser = TestApis.users().instrumented();
@@ -773,7 +826,8 @@
}
if (hasProfileOwner) {
- ensureHasProfileOwner(instrumentedUser, dpcIsPrimary, affiliationIds);
+ ensureHasProfileOwner(
+ instrumentedUser, dpcIsPrimary, useParentInstance, affiliationIds);
} else {
ensureHasNoProfileOwner(instrumentedUser);
}
@@ -799,38 +853,46 @@
!TestApis.packages().features().contains(feature), failureMode);
}
- private void requireNoGmsInstrumentation() {
- boolean instrumentingGms =
- TestApis.context().instrumentedContext().getPackageName().equals(GMS_PKG);
+ private void requireNoPermissionsInstrumentation() {
+ boolean instrumentingPermissions =
+ TestApis.context()
+ .instrumentedContext().getPackageName()
+ .equals(mPermissionsInstrumentationPackage);
checkFailOrSkip(
- "This test never runs using gms instrumentation",
- !instrumentingGms,
+ "This test never runs using permissions instrumentation on this version"
+ + " of Android",
+ !instrumentingPermissions,
FailureMode.SKIP
);
}
- private void requireGmsInstrumentation(int min, int max) {
- mHasRequireGmsInstrumentation = true;
- boolean instrumentingGms =
- TestApis.context().instrumentedContext().getPackageName().equals(GMS_PKG);
+ private void requirePermissionsInstrumentation() {
+ mHasRequirePermissionInstrumentation = true;
+ boolean instrumentingPermissions =
+ TestApis.context()
+ .instrumentedContext().getPackageName()
+ .equals(mPermissionsInstrumentationPackage);
- if (meetsSdkVersionRequirements(min, max)) {
- checkFailOrSkip(
- "For SDK versions between " + min + " and " + max
- + " (inclusive), this test only runs when using gms instrumentation",
- instrumentingGms,
- FailureMode.SKIP
- );
- } else {
- checkFailOrSkip(
- "For SDK versions between " + min + " and " + max
- + " (inclusive), this test only runs when not using gms "
- + "instrumentation",
- !instrumentingGms,
- FailureMode.SKIP
- );
- }
+ checkFailOrSkip(
+ "This test only runs when using permissions instrumentation on this"
+ + " version of Android",
+ instrumentingPermissions,
+ FailureMode.SKIP
+ );
+ }
+
+ private void requireTargetSdkVersion(
+ int min, int max, FailureMode failureMode) {
+
+ int targetSdkVersion = TestApis.packages().instrumented().targetSdkVersion();
+
+ checkFailOrSkip(
+ "TargetSdkVersion must be between " + min + " and " + max
+ + " (inclusive) (version is " + targetSdkVersion + ")",
+ min <= targetSdkVersion && max >= targetSdkVersion,
+ failureMode
+ );
}
private void requireSdkVersion(int min, int max, FailureMode failureMode) {
@@ -881,17 +943,6 @@
}
}
- public enum UserType {
- /** Only to be used with annotations. */
- ANY,
- SYSTEM_USER,
- CURRENT_USER,
- PRIMARY_USER,
- SECONDARY_USER,
- WORK_PROFILE,
- TV_PROFILE,
- }
-
private static final String LOG_TAG = "DeviceState";
private static final Context sContext = TestApis.context().instrumentedContext();
@@ -906,6 +957,7 @@
private final List<UserReference> mCreatedUsers = new ArrayList<>();
private final List<UserBuilder> mRemovedUsers = new ArrayList<>();
+ private final List<UserReference> mUsersSetPasswords = new ArrayList<>();
private final List<BlockingBroadcastReceiver> mRegisteredBroadcastReceivers = new ArrayList<>();
private boolean mHasChangedDeviceOwner = false;
private DevicePolicyController mOriginalDeviceOwner;
@@ -1134,6 +1186,7 @@
UserType forUser,
boolean hasProfileOwner,
boolean profileOwnerIsPrimary,
+ boolean useParentInstance,
OptionalBoolean switchedToParentUser) {
com.android.bedstead.nene.users.UserType resolvedUserType =
requireUserSupported(profileType, FailureMode.SKIP);
@@ -1166,7 +1219,8 @@
mProfiles.get(resolvedUserType).put(forUserReference, profile);
if (hasProfileOwner) {
- ensureHasProfileOwner(profile, profileOwnerIsPrimary, /* affiliationIds= */ null);
+ ensureHasProfileOwner(
+ profile, profileOwnerIsPrimary, useParentInstance, /* affiliationIds= */ null);
}
ensureSwitchedToUser(switchedToParentUser, forUserReference);
@@ -1340,7 +1394,9 @@
mDeviceOwner.remove();
}
} else if (!mOriginalDeviceOwner.equals(mDeviceOwner)) {
- mDeviceOwner.remove();
+ if (mDeviceOwner != null) {
+ mDeviceOwner.remove();
+ }
TestApis.devicePolicy().setDeviceOwner(
mOriginalDeviceOwner.componentName());
}
@@ -1369,6 +1425,15 @@
}
mChangedProfileOwners.clear();
+ for (UserReference user : mUsersSetPasswords) {
+ if (mCreatedUsers.contains(user)) {
+ continue; // Will be removed anyway
+ }
+ user.clearPassword();
+ }
+
+ mUsersSetPasswords.clear();
+
for (UserReference user : mCreatedUsers) {
user.remove();
}
@@ -1586,14 +1651,16 @@
.setAffiliationIds(REMOTE_DPC_COMPONENT_NAME, affiliationIds);
}
- private void ensureHasProfileOwner(UserType onUser, boolean isPrimary, Set<String> affiliationIds) {
+ private void ensureHasProfileOwner(UserType onUser, boolean isPrimary,
+ boolean useParentInstance, Set<String> affiliationIds) {
// TODO(scottjonathan): Should support non-remotedpc profile owner (default to remotedpc)
UserReference user = resolveUserTypeToUser(onUser);
- ensureHasProfileOwner(user, isPrimary, affiliationIds);
+ ensureHasProfileOwner(user, isPrimary, useParentInstance, affiliationIds);
}
private void ensureHasProfileOwner(
- UserReference user, boolean isPrimary, Set<String> affiliationIds) {
+ UserReference user, boolean isPrimary, boolean useParentInstance,
+ Set<String> affiliationIds) {
if (isPrimary && mPrimaryPolicyManager != null
&& !user.equals(mPrimaryPolicyManager.user())) {
throw new IllegalStateException("Only one DPC can be marked as primary per test");
@@ -1625,7 +1692,14 @@
}
if (isPrimary) {
- mPrimaryPolicyManager = RemoteDpc.forDevicePolicyController(mProfileOwners.get(user));
+ if (useParentInstance) {
+ mPrimaryPolicyManager = new RemoteDpcUsingParentInstance(
+ RemoteDpc.forDevicePolicyController(
+ mProfileOwners.get(user)).devicePolicyController());
+ } else {
+ mPrimaryPolicyManager =
+ RemoteDpc.forDevicePolicyController(mProfileOwners.get(user));
+ }
}
if (affiliationIds != null) {
@@ -1833,16 +1907,39 @@
}
private void ensureCanGetPermission(String permission) {
- // TODO(scottjonathan): Apply gms permission switches automatically rather than hard-coding
- // TODO(scottjonathan): Add a config to only enforce gms permission when needed
- if (permission.equals(NOTIFY_PENDING_SYSTEM_UPDATE)) {
- requireGmsInstrumentation(1, Build.VERSION_CODES.R);
+ if (mPermissionsInstrumentationPackage == null) {
+ // We just need to check if we can get it generally
+
+ if (TestApis.permissions().usablePermissions().contains(permission)) {
+ return;
+ }
+
+ if (TestApis.packages().instrumented().isInstantApp()) {
+ // Instant Apps aren't able to know the permissions of shell so we can't know if we
+ // can adopt it - we'll assume we can adopt and log
+ Log.i(LOG_TAG,
+ "Assuming we can get permission " + permission
+ + " as running on instant app");
+ return;
+ }
+
+ TestApis.permissions().throwPermissionException(
+ "Can not get required permission", permission);
}
- // TODO(scottjonathan): Apply version-specific constraints automatically
- if (permission.equals(INTERACT_ACROSS_USERS_FULL)) {
- requireSdkVersion(
- Build.VERSION_CODES.Q, Integer.MAX_VALUE, FailureMode.SKIP,
- "This test requires INTERACT_ACROSS_USERS_FULL which can only be used on Q+");
+
+ if (TestApis.permissions().adoptablePermissions().contains(permission)) {
+ requireNoPermissionsInstrumentation();
+ } else if (mPermissionsInstrumentationPackagePermissions.contains(permission)) {
+ requirePermissionsInstrumentation();
+ } else {
+ // Can't get permission at all - error (including the permissions for both)
+ TestApis.permissions().throwPermissionException(
+ "Can not get permission including by instrumenting "
+ + mPermissionsInstrumentationPackage
+ + "\n " + mPermissionsInstrumentationPackage + " permissions: "
+ + mPermissionsInstrumentationPackagePermissions,
+ permission
+ );
}
}
@@ -1911,4 +2008,41 @@
.isLowRamDevice(),
failureMode);
}
+
+ private void ensureScreenIsOn() {
+ TestApis.device().wakeUp();
+ }
+
+ private void ensurePasswordSet(UserType forUser, String password) {
+ UserReference user = resolveUserTypeToUser(forUser);
+
+ if (user.hasPassword()) {
+ return;
+ }
+
+ try {
+ user.setPassword(password);
+ } catch (NeneException e) {
+ throw new AssertionError("Require password set but error when setting password", e);
+ }
+ mUsersSetPasswords.add(user);
+ }
+
+ private void ensurePasswordNotSet(UserType forUser) {
+ UserReference user = resolveUserTypeToUser(forUser);
+
+ if (!user.hasPassword()) {
+ return;
+ }
+
+ try {
+ user.clearPassword(DEFAULT_PASSWORD);
+ } catch (NeneException e
+ ) {
+ throw new AssertionError(
+ "Test requires user " + user + " does not have a password. "
+ + "Password is set and is not DEFAULT_PASSWORD.");
+ }
+ mUsersSetPasswords.remove(user);
+ }
}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/Policy.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/Policy.java
deleted file mode 100644
index e01c8f1..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/Policy.java
+++ /dev/null
@@ -1,519 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier;
-
-import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
-import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL;
-import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
-import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_SELECTION;
-import static android.app.admin.DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP;
-import static android.app.admin.DevicePolicyManager.DELEGATION_INSTALL_EXISTING_PACKAGE;
-import static android.app.admin.DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES;
-import static android.app.admin.DevicePolicyManager.DELEGATION_NETWORK_LOGGING;
-import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
-import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
-import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_AFFILIATED_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_AFFILIATED_PROFILE_OWNER_PROFILE;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_AFFILIATED_PROFILE_OWNER_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_IN_BACKGROUND;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_AFFILIATED_OTHER_USERS;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_PARENT;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_UNAFFILIATED_OTHER_USERS;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.CAN_BE_DELEGATED;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.DO_NOT_APPLY_TO_NEGATIVE_TESTS;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.NO;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDelegate;
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeNone;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnAffiliatedDeviceOwnerSecondaryUser;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnAffiliatedProfileOwnerSecondaryUser;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnBackgroundDeviceOwnerUser;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnDeviceOwnerUser;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnParentOfCorporateOwnedProfileOwner;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnProfileOwnerPrimaryUser;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnProfileOwnerProfileWithNoDeviceOwner;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnUnaffiliatedProfileOwnerSecondaryUser;
-
-import com.google.auto.value.AutoAnnotation;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-
-import java.lang.annotation.Annotation;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
-/**
- * Utility class for enterprise policy tests.
- */
-public final class Policy {
-
- // Delegate scopes to be used for a "CannotSet" state. All delegate scopes except the ones which
- // should allow use of the API will be granted
- private static final ImmutableSet<String> ALL_DELEGATE_SCOPES = ImmutableSet.of(
- DELEGATION_CERT_INSTALL,
- DELEGATION_APP_RESTRICTIONS,
- DELEGATION_BLOCK_UNINSTALL,
- DELEGATION_PERMISSION_GRANT,
- DELEGATION_PACKAGE_ACCESS,
- DELEGATION_ENABLE_SYSTEM_APP,
- DELEGATION_INSTALL_EXISTING_PACKAGE,
- DELEGATION_KEEP_UNINSTALLED_PACKAGES,
- DELEGATION_NETWORK_LOGGING,
- DELEGATION_CERT_SELECTION,
- DELEGATION_SECURITY_LOGGING
- );
-
- // This is a map containing all Include* annotations and the flags which lead to them
- // This is not validated - every state must have a single APPLIED_BY annotation
- private static final ImmutableMap<Integer, Function<EnterprisePolicy, Set<Annotation>>>
- STATE_ANNOTATIONS =
- ImmutableMap.<Integer, Function<EnterprisePolicy, Set<Annotation>>>builder()
- .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnDeviceOwnerUser()))
- .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnDeviceOwnerUser(), /* isPrimary= */ true))
- .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIES_IN_BACKGROUND, singleAnnotation(includeRunOnBackgroundDeviceOwnerUser()))
- .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIES_IN_BACKGROUND | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnBackgroundDeviceOwnerUser(), /* isPrimary= */ true))
-
- .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_UNAFFILIATED_OTHER_USERS, singleAnnotation(includeRunOnNonAffiliatedDeviceOwnerSecondaryUser()))
- .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_UNAFFILIATED_OTHER_USERS | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnNonAffiliatedDeviceOwnerSecondaryUser(), /* isPrimary= */ true))
- .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_AFFILIATED_OTHER_USERS, singleAnnotation(includeRunOnAffiliatedDeviceOwnerSecondaryUser()))
- .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_AFFILIATED_OTHER_USERS | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnAffiliatedDeviceOwnerSecondaryUser(), /* isPrimary= */ true))
-
- .put(APPLIED_BY_AFFILIATED_PROFILE_OWNER_USER | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnAffiliatedProfileOwnerSecondaryUser()))
- .put(APPLIED_BY_AFFILIATED_PROFILE_OWNER_USER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnAffiliatedProfileOwnerSecondaryUser(), /* isPrimary= */ true))
- .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnUnaffiliatedProfileOwnerSecondaryUser()))
- .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnUnaffiliatedProfileOwnerSecondaryUser(), /* isPrimary= */ true))
- .put(APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnProfileOwnerPrimaryUser()))
- .put(APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnProfileOwnerPrimaryUser(), /* isPrimary= */ true))
-
- .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnProfileOwnerProfileWithNoDeviceOwner()))
- .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnProfileOwnerProfileWithNoDeviceOwner(), /* isPrimary= */ true))
- .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_PARENT, singleAnnotation(includeRunOnParentOfProfileOwnerWithNoDeviceOwner()))
- .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_PARENT | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnParentOfProfileOwnerWithNoDeviceOwner(), /* isPrimary= */ true))
-
- .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_UNAFFILIATED_OTHER_USERS, singleAnnotation(includeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile()))
- .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_UNAFFILIATED_OTHER_USERS | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile(), /* isPrimary= */ true))
- .build();
- // This must contain one key for every APPLIED_BY that is being used, and maps to the
- // "default" for testing that DPC type
- // in general this will be a state which runs on the same user as the dpc.
- private static final ImmutableMap<Integer, Function<EnterprisePolicy, Set<Annotation>>>
- DPC_STATE_ANNOTATIONS_BASE =
- ImmutableMap.<Integer, Function<EnterprisePolicy, Set<Annotation>>>builder()
- .put(APPLIED_BY_DEVICE_OWNER, (flags) -> hasFlag(flags.dpc(), APPLIED_BY_DEVICE_OWNER | APPLIES_IN_BACKGROUND) ? ImmutableSet.of(includeRunOnBackgroundDeviceOwnerUser()) : ImmutableSet.of(includeRunOnDeviceOwnerUser()))
- .put(APPLIED_BY_AFFILIATED_PROFILE_OWNER, singleAnnotation(includeRunOnAffiliatedProfileOwnerSecondaryUser()))
- .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER, singleAnnotation(includeRunOnProfileOwnerPrimaryUser()))
- .put(APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO, singleAnnotation(includeRunOnProfileOwnerPrimaryUser()))
- .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE, singleAnnotation(includeRunOnProfileOwnerProfileWithNoDeviceOwner()))
- .build();
- private static final Map<Integer, Function<EnterprisePolicy, Set<Annotation>>>
- DPC_STATE_ANNOTATIONS = DPC_STATE_ANNOTATIONS_BASE.entrySet().stream()
- .collect(Collectors.toMap(Map.Entry::getKey, Policy::addGeneratedStates));
- private static final int APPLIED_BY_FLAGS =
- APPLIED_BY_DEVICE_OWNER | APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE
- | APPLIED_BY_AFFILIATED_PROFILE_OWNER_PROFILE
- | APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER
- | APPLIED_BY_AFFILIATED_PROFILE_OWNER_USER;
- private static final Map<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>>
- ANNOTATIONS_MAP = calculateAnnotationsMap(STATE_ANNOTATIONS);
-
- private Policy() {
-
- }
-
- @AutoAnnotation
- private static IncludeNone includeNone() {
- return new AutoAnnotation_Policy_includeNone();
- }
-
- @AutoAnnotation
- private static IncludeRunOnDeviceOwnerUser includeRunOnDeviceOwnerUser() {
- return new AutoAnnotation_Policy_includeRunOnDeviceOwnerUser();
- }
-
- @AutoAnnotation
- private static IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser includeRunOnNonAffiliatedDeviceOwnerSecondaryUser() {
- return new AutoAnnotation_Policy_includeRunOnNonAffiliatedDeviceOwnerSecondaryUser();
- }
-
- @AutoAnnotation
- private static IncludeRunOnAffiliatedDeviceOwnerSecondaryUser includeRunOnAffiliatedDeviceOwnerSecondaryUser() {
- return new AutoAnnotation_Policy_includeRunOnAffiliatedDeviceOwnerSecondaryUser();
- }
-
- @AutoAnnotation
- private static IncludeRunOnAffiliatedProfileOwnerSecondaryUser includeRunOnAffiliatedProfileOwnerSecondaryUser() {
- return new AutoAnnotation_Policy_includeRunOnAffiliatedProfileOwnerSecondaryUser();
- }
-
- @AutoAnnotation
- private static IncludeRunOnUnaffiliatedProfileOwnerSecondaryUser includeRunOnUnaffiliatedProfileOwnerSecondaryUser() {
- return new AutoAnnotation_Policy_includeRunOnUnaffiliatedProfileOwnerSecondaryUser();
- }
-
- @AutoAnnotation
- private static IncludeRunOnProfileOwnerProfileWithNoDeviceOwner includeRunOnProfileOwnerProfileWithNoDeviceOwner() {
- return new AutoAnnotation_Policy_includeRunOnProfileOwnerProfileWithNoDeviceOwner();
- }
-
- @AutoAnnotation
- private static IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile includeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile() {
- return new AutoAnnotation_Policy_includeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile();
- }
-
- @AutoAnnotation
- private static IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner includeRunOnParentOfProfileOwnerWithNoDeviceOwner() {
- return new AutoAnnotation_Policy_includeRunOnParentOfProfileOwnerWithNoDeviceOwner();
- }
-
- @AutoAnnotation
- private static IncludeRunOnParentOfCorporateOwnedProfileOwner includeRunOnParentOfCorporateOwnedProfileOwner() {
- return new AutoAnnotation_Policy_includeRunOnParentOfCorporateOwnedProfileOwner();
- }
-
- @AutoAnnotation
- private static IncludeRunOnProfileOwnerPrimaryUser includeRunOnProfileOwnerPrimaryUser() {
- return new AutoAnnotation_Policy_includeRunOnProfileOwnerPrimaryUser();
- }
-
- @AutoAnnotation
- private static IncludeRunOnBackgroundDeviceOwnerUser includeRunOnBackgroundDeviceOwnerUser() {
- return new AutoAnnotation_Policy_includeRunOnBackgroundDeviceOwnerUser();
- }
-
- @AutoAnnotation
- private static EnsureHasDelegate ensureHasDelegate(EnsureHasDelegate.AdminType admin,
- String[] scopes, boolean isPrimary) {
- return new AutoAnnotation_Policy_ensureHasDelegate(admin, scopes, isPrimary);
- }
-
- private static Function<EnterprisePolicy, Set<Annotation>> singleAnnotation(
- Annotation annotation) {
- return (i) -> ImmutableSet.of(annotation);
- }
-
- private static Function<EnterprisePolicy, Set<Annotation>> generateDelegateAnnotation(
- Annotation annotation, boolean isPrimary) {
- return (policy) -> {
- Annotation[] existingAnnotations = annotation.annotationType().getAnnotations();
- return Arrays.stream(policy.delegatedScopes())
- .map(scope -> {
- Annotation[] newAnnotations = Arrays.copyOf(existingAnnotations,
- existingAnnotations.length + 1);
- newAnnotations[newAnnotations.length - 1] = ensureHasDelegate(
- EnsureHasDelegate.AdminType.PRIMARY, new String[]{scope},
- isPrimary);
-
- return new DynamicParameterizedAnnotation(
- annotation.annotationType().getSimpleName() + "Delegate:" + scope,
- newAnnotations);
- }).collect(Collectors.toSet());
- };
- }
-
- private static Map<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>> calculateAnnotationsMap(
- Map<Integer, Function<EnterprisePolicy, Set<Annotation>>> annotations) {
- Map<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>> b = new HashMap<>();
-
- for (Map.Entry<Integer, Function<EnterprisePolicy, Set<Annotation>>> i :
- annotations.entrySet()) {
- if (!b.containsKey(i.getValue())) {
- b.put(i.getValue(), new HashSet<>());
- }
-
- b.get(i.getValue()).add(i.getKey());
- }
-
- return b;
- }
-
- private static Function<EnterprisePolicy, Set<Annotation>> addGeneratedStates(
- ImmutableMap.Entry<Integer, Function<EnterprisePolicy, Set<Annotation>>> entry) {
- return (policy) -> {
- if (hasFlag(policy.dpc(), entry.getKey() | CAN_BE_DELEGATED)) {
- Set<Annotation> results = new HashSet<>(entry.getValue().apply(policy));
- results.addAll(results.stream().flatMap(
- t -> generateDelegateAnnotation(t, /* isPrimary= */ true).apply(
- policy).stream())
- .collect(Collectors.toSet()));
-
- return results;
- }
-
- return entry.getValue().apply(policy);
- };
- }
-
-
- /**
- * Get parameterized test runs for the given policy.
- *
- * <p>These are states which should be run where the policy is able to be applied.
- */
- public static List<Annotation> positiveStates(String policyName,
- EnterprisePolicy enterprisePolicy) {
- Set<Annotation> annotations = new HashSet<>();
-
- validateFlags(policyName, enterprisePolicy.dpc());
-
- for (Map.Entry<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>> annotation :
- ANNOTATIONS_MAP.entrySet()) {
- if (isPositive(enterprisePolicy.dpc(), annotation.getValue())) {
- annotations.addAll(annotation.getKey().apply(enterprisePolicy));
- }
- }
-
- if (annotations.isEmpty()) {
- // Don't run the original test unparameterized
- annotations.add(includeNone());
- }
-
- return new ArrayList<>(annotations);
- }
-
- private static boolean isPositive(int[] policyFlags, Set<Integer> annotationFlags) {
- for (int annotationFlag : annotationFlags) {
- if (hasFlag(policyFlags, annotationFlag)) {
- return true;
- }
- }
- return false;
- }
-
- private static boolean isNegative(int[] policyFlags, Set<Integer> annotationFlags) {
- for (int annotationFlag : annotationFlags) {
- if (hasFlag(annotationFlag, DO_NOT_APPLY_TO_NEGATIVE_TESTS, /* nonMatchingFlag= */
- NO)) {
- return false; // We don't support using this annotation for negative tests
- }
-
- int appliedByFlag = APPLIED_BY_FLAGS & annotationFlag;
- int otherFlags = annotationFlag ^ appliedByFlag; // remove the appliedByFlag
- if (hasFlag(policyFlags, /* matchingFlag= */ appliedByFlag, /* nonMatchingFlag= */
- otherFlags)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Get negative parameterized test runs for the given policy.
- *
- * <p>These are states which should be run where the policy is not able to be applied.
- */
- public static List<Annotation> negativeStates(String policyName,
- EnterprisePolicy enterprisePolicy) {
- Set<Annotation> annotations = new HashSet<>();
-
- validateFlags(policyName, enterprisePolicy.dpc());
-
- for (Map.Entry<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>> annotation :
- ANNOTATIONS_MAP.entrySet()) {
- if (isNegative(enterprisePolicy.dpc(), annotation.getValue())) {
- annotations.addAll(annotation.getKey().apply(enterprisePolicy));
- }
- }
-
- if (annotations.isEmpty()) {
- // Don't run the original test unparameterized
- annotations.add(includeNone());
- }
-
- return new ArrayList<>(annotations);
- }
-
- /**
- * Get parameterized test runs where the policy cannot be set for the given policy.
- */
- public static List<Annotation> cannotSetPolicyStates(String policyName,
- EnterprisePolicy enterprisePolicy, boolean includeDeviceAdminStates,
- boolean includeNonDeviceAdminStates) {
- Set<Annotation> annotations = new HashSet<>();
-
- validateFlags(policyName, enterprisePolicy.dpc());
-
- if (includeDeviceAdminStates) {
- int allFlags = 0;
- for (int p : enterprisePolicy.dpc()) {
- allFlags = allFlags | p;
- }
-
- for (Map.Entry<Integer, Function<EnterprisePolicy, Set<Annotation>>> appliedByFlag :
- DPC_STATE_ANNOTATIONS.entrySet()) {
- if ((appliedByFlag.getKey() & allFlags) == 0) {
- annotations.addAll(appliedByFlag.getValue().apply(enterprisePolicy));
- }
- }
- }
-
- if (includeNonDeviceAdminStates) {
- Set<String> validScopes = ImmutableSet.copyOf(enterprisePolicy.delegatedScopes());
- String[] scopes = ALL_DELEGATE_SCOPES.stream()
- .filter(i -> !validScopes.contains(i))
- .toArray(String[]::new);
- Annotation[] existingAnnotations = IncludeRunOnDeviceOwnerUser.class.getAnnotations();
-
- if (BedsteadJUnit4.isDebug()) {
- // Add a non-DPC with no delegate scopes
- Annotation[] newAnnotations = Arrays.copyOf(existingAnnotations,
- existingAnnotations.length + 1);
- newAnnotations[newAnnotations.length - 1] = ensureHasDelegate(
- EnsureHasDelegate.AdminType.PRIMARY, new String[]{}, /* isPrimary= */ true);
- annotations.add(
- new DynamicParameterizedAnnotation("DelegateWithNoScopes", newAnnotations));
-
- for (String scope : scopes) {
- newAnnotations = Arrays.copyOf(existingAnnotations,
- existingAnnotations.length + 1);
- newAnnotations[newAnnotations.length - 1] = ensureHasDelegate(
- EnsureHasDelegate.AdminType.PRIMARY, new String[]{scope}, /* isPrimary= */ true);
- annotations.add(
- new DynamicParameterizedAnnotation("DelegateWithScope:" + scope, newAnnotations));
- }
- } else {
- Annotation[] newAnnotations = Arrays.copyOf(existingAnnotations,
- existingAnnotations.length + 1);
- newAnnotations[newAnnotations.length - 1] = ensureHasDelegate(
- EnsureHasDelegate.AdminType.PRIMARY, scopes, /* isPrimary= */ true);
- annotations.add(
- new DynamicParameterizedAnnotation("DelegateWithoutValidScope", newAnnotations));
- }
- }
-
- if (annotations.isEmpty()) {
- // Don't run the original test unparameterized
- annotations.add(includeNone());
- }
-
- return new ArrayList<>(annotations);
- }
-
- /**
- * Get state annotations where the policy can be set for the given policy.
- */
- public static List<Annotation> canSetPolicyStates(
- String policyName, EnterprisePolicy enterprisePolicy, boolean singleTestOnly) {
- Set<Annotation> annotations = new HashSet<>();
-
- validateFlags(policyName, enterprisePolicy.dpc());
-
- int allFlags = 0;
- for (int p : enterprisePolicy.dpc()) {
- allFlags = allFlags | p;
- }
-
- for (Map.Entry<Integer, Function<EnterprisePolicy, Set<Annotation>>> appliedByFlag :
- DPC_STATE_ANNOTATIONS.entrySet()) {
- if ((appliedByFlag.getKey() & allFlags) == appliedByFlag.getKey()) {
- annotations.addAll(appliedByFlag.getValue().apply(enterprisePolicy));
- }
- }
-
- if (annotations.isEmpty()) {
- // Don't run the original test unparameterized
- annotations.add(includeNone());
- }
-
- List<Annotation> annotationList = new ArrayList<>(annotations);
-
- if (singleTestOnly) {
- // We select one annotation in an arbitrary but deterministic way
- annotationList.sort(Comparator.comparing(
- a -> a instanceof DynamicParameterizedAnnotation
- ? "DynamicParameterizedAnnotation" : a.annotationType().getName()));
-
- // We don't want a delegate to be the representative test
- Annotation firstAnnotation = annotationList.stream()
- .filter(i -> !(i instanceof DynamicParameterizedAnnotation))
- .findFirst().get();
- annotationList.clear();
- annotationList.add(firstAnnotation);
- }
-
- return annotationList;
- }
-
- private static void validateFlags(String policyName, int[] values) {
- int usedAppliedByFlags = 0;
-
- for (int value : values) {
- validateFlags(policyName, value);
- int newUsedAppliedByFlags = usedAppliedByFlags | (value & APPLIED_BY_FLAGS);
- if (newUsedAppliedByFlags == usedAppliedByFlags) {
- throw new IllegalStateException(
- "Cannot have more than one policy flag APPLIED by the same component. "
- + "Error in policy " + policyName);
- }
- usedAppliedByFlags = newUsedAppliedByFlags;
- }
- }
-
- private static void validateFlags(String policyName, int value) {
- int matchingAppliedByFlags = APPLIED_BY_FLAGS & value;
-
- if (matchingAppliedByFlags == 0) {
- throw new IllegalStateException(
- "All policy flags must specify 1 APPLIED_BY flag. Policy " + policyName
- + " did not.");
- }
- }
-
- private static boolean hasFlag(int[] values, int matchingFlag) {
- return hasFlag(values, matchingFlag, /* nonMatchingFlag= */ NO);
- }
-
- private static boolean hasFlag(int[] values, int matchingFlag, int nonMatchingFlag) {
- for (int value : values) {
- if (hasFlag(value, matchingFlag, nonMatchingFlag)) {
- return true;
- }
- }
- return false;
- }
-
- private static boolean hasFlag(int value, int matchingFlag, int nonMatchingFlag) {
- if (!((value & matchingFlag) == matchingFlag)) {
- return false;
- }
-
- if (nonMatchingFlag != NO) {
- return (value & nonMatchingFlag) != nonMatchingFlag;
- }
-
- return true;
- }
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/AfterClass.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/AfterClass.java
deleted file mode 100644
index 1241503..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/AfterClass.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Replacement for {@link org.junit.AfterClass} for use by classes which use {@link DeviceState}.
- *
- * <p>Methods annotated {@link AfterClass} must be public, static, must return {@code void}, and
- * must take no arguments.
- *
- * <p>The annotated method will be called after all tests, once per class.
- *
- * <p>The state prior to calling this method is not guaranteed, as test methods may have changed the
- * state. If any class-level annotation assumptions are violated this method will not be run.
- *
- * <p>If there are multiple methods annotated {@code @AfterClass} there is no guarantee as to the
- * order they will be executed.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.METHOD)
-public @interface AfterClass {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/BeforeClass.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/BeforeClass.java
deleted file mode 100644
index 5f76ba6..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/BeforeClass.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Replacement for {@link org.junit.BeforeClass} for use by classes which use {@link DeviceState}.
- *
- * <p>Methods annotated {@link BeforeClass} must be public, static, must return {@code void}, and
- * must take no arguments.
- *
- * <p>The annotated method will be called before any tests, once per class. Class-level Bedstead
- * annotations will be processed before running the method, and if any assumptions are violated the
- * method will not be run.
- *
- * <p>If there are multiple methods annotated {@code @BeforeClass} there is no guarantee as to the
- * order they will be executed.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.METHOD)
-public @interface BeforeClass {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoSecondaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoSecondaryUser.java
deleted file mode 100644
index 3cb8104..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoSecondaryUser.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.meta.EnsureHasNoUserAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on a device which has no secondary user that is not the
- * instrumented user.
- *
- * <p>Your test configuration may be configured so that this test is only run on a device which
- * has no secondary user that is not the current user. Otherwise, you can use {@link DeviceState}
- * to ensure that the device enters the correct state for the method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@EnsureHasNoUserAnnotation("android.os.usertype.full.SECONDARY")
-public @interface EnsureHasNoSecondaryUser {
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default MIDDLE;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoTvProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoTvProfile.java
deleted file mode 100644
index 50dac4d..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoTvProfile.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.meta.EnsureHasNoProfileAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on a user which has no Tv profile.
- *
- * <p>Your test configuration may be configured so that this test is only run on a user which has
- * no Tv profile. Otherwise, you can use {@link DeviceState} to ensure that the device enters
- * the correct state for the method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@EnsureHasNoProfileAnnotation("com.android.tv.profile")
-public @interface EnsureHasNoTvProfile {
- /** Which user type the tv profile should not be attached to. */
- DeviceState.UserType forUser() default CURRENT_USER;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default MIDDLE;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoWorkProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoWorkProfile.java
deleted file mode 100644
index bec8a6b..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoWorkProfile.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.meta.EnsureHasNoProfileAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on a user which does not have a work profile.
- *
- * <p>Your test configuration may be configured so that this test is only run on a user which has
- * no work profile. Otherwise, you can use {@link DeviceState} to ensure that the device enters
- * the correct state for the method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@EnsureHasNoProfileAnnotation("android.os.usertype.profile.MANAGED")
-@RequireFeature("android.software.managed_users")
-public @interface EnsureHasNoWorkProfile {
- /** Which user type the work profile should not be attached to. */
- DeviceState.UserType forUser() default CURRENT_USER;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default MIDDLE;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java
deleted file mode 100644
index 186d86b..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.OptionalBoolean.ANY;
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-import com.android.bedstead.harrier.annotations.meta.EnsureHasUserAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on a device which has a secondary user.
- *
- * <p>Your test configuration may be configured so that this test is only run on a device which
- * has a secondary user that is not the current user. Otherwise, you can use {@link DeviceState}
- * to ensure that the device enters the correct state for the method. If there is not already a
- * secondary user on the device, and the device does not support creating additional users, then
- * the test will be skipped.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@EnsureHasUserAnnotation("android.os.usertype.full.SECONDARY")
-public @interface EnsureHasSecondaryUser {
- /** Whether the instrumented test app should be installed in the secondary user. */
- OptionalBoolean installInstrumentedApp() default TRUE;
-
- /**
- * Should we ensure that we are switched to the given user
- */
- OptionalBoolean switchedToUser() default ANY;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default MIDDLE;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java
deleted file mode 100644
index 35f3283..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-import static com.android.bedstead.harrier.OptionalBoolean.ANY;
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-import com.android.bedstead.harrier.annotations.meta.EnsureHasProfileAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on a user which has a Tv profile.
- *
- * <p>Your test configuration may be configured so that this test is only run on a user which has
- * a Tv profile. Otherwise, you can use {@link DeviceState} to ensure that the device enters
- * the correct state for the method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@EnsureHasProfileAnnotation("com.android.tv.profile")
-public @interface EnsureHasTvProfile {
- /** Which user type the tv profile should be attached to. */
- DeviceState.UserType forUser() default CURRENT_USER;
-
- /** Whether the instrumented test app should be installed in the tv profile. */
- OptionalBoolean installInstrumentedApp() default TRUE;
-
- /**
- * Should we ensure that we are switched to the parent of the profile.
- */
- OptionalBoolean switchedToParentUser() default ANY;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default MIDDLE;
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java
deleted file mode 100644
index 892b7f4..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-import static com.android.bedstead.harrier.OptionalBoolean.ANY;
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner;
-import com.android.bedstead.harrier.annotations.meta.EnsureHasProfileAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on a user which has a work profile.
- *
- * <p>Use of this annotation implies
- * {@code RequireFeature("android.software.managed_users", SKIP)}.
- *
- * <p>Your test configuration may be configured so that this test is only run on a user which has
- * a work profile. Otherwise, you can use {@link DeviceState} to ensure that the device enters
- * the correct state for the method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@EnsureHasProfileAnnotation(value = "android.os.usertype.profile.MANAGED", hasProfileOwner = true)
-@RequireFeature("android.software.managed_users")
-@EnsureHasNoDeviceOwner // TODO: This should only apply on Android R+
-public @interface EnsureHasWorkProfile {
- /** Which user type the work profile should be attached to. */
- DeviceState.UserType forUser() default CURRENT_USER;
-
- /** Whether the instrumented test app should be installed in the work profile. */
- OptionalBoolean installInstrumentedApp() default TRUE;
-
- /**
- * Whether the profile owner's DPC should be returned by calls to {@link DeviceState#dpc()}.
- *
- * <p>Only one device policy controller per test should be marked as primary.
- */
- boolean dpcIsPrimary() default false;
-
- /**
- * Should we ensure that we are switched to the parent of the profile.
- */
- OptionalBoolean switchedToParentUser() default ANY;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default MIDDLE;
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackageNotInstalled.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackageNotInstalled.java
deleted file mode 100644
index 9ce07a7..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackageNotInstalled.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Repeatable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run only when a given package is not installed.
- *
- * <p>You can guarantee that these methods do not run on devices with the package by
- * using {@code DeviceState}.
- *
- * <p>Tests annotated with this will attempt to remove the package if it exists, or otherwise fail.
- * If you'd rather skip or fail tests immediately without attempting to remove see
- * {@link RequirePackageNotInstalled}.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@Repeatable(EnsurePackagesNotInstalled.class)
-public @interface EnsurePackageNotInstalled {
- String value();
- DeviceState.UserType onUser() default DeviceState.UserType.CURRENT_USER;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default MIDDLE;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireAospBuild.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireAospBuild.java
deleted file mode 100644
index 830fa10..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireAospBuild.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-import static com.android.bedstead.harrier.annotations.RequireAospBuild.GMS_CORE_PACKAGE;
-import static com.android.bedstead.harrier.annotations.RequireAospBuild.GSF_PACKAGE;
-import static com.android.bedstead.harrier.annotations.RequireAospBuild.PLAY_STORE_PACKAGE;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequirePackageNotInstalled(value = GMS_CORE_PACKAGE, onUser = DeviceState.UserType.ANY)
-@RequirePackageNotInstalled(value = PLAY_STORE_PACKAGE, onUser = DeviceState.UserType.ANY)
-@RequirePackageNotInstalled(value = GSF_PACKAGE, onUser = DeviceState.UserType.ANY)
-public @interface RequireAospBuild {
- String GMS_CORE_PACKAGE = "com.google.android.gms";
- String PLAY_STORE_PACKAGE = "com.android.vending";
- String GSF_PACKAGE = "com.google.android.gsf";
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeatures.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeatures.java
deleted file mode 100644
index bbef533..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeatures.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.annotations.meta.RepeatingAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RepeatingAnnotation
-public @interface RequireDoesNotHaveFeatures {
- RequireDoesNotHaveFeature[] value();
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default EARLY;
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsBuild.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsBuild.java
deleted file mode 100644
index 48e7984..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsBuild.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-import static com.android.bedstead.harrier.annotations.RequireAospBuild.GMS_CORE_PACKAGE;
-import static com.android.bedstead.harrier.annotations.RequireAospBuild.GSF_PACKAGE;
-import static com.android.bedstead.harrier.annotations.RequireAospBuild.PLAY_STORE_PACKAGE;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequirePackageInstalled(value = GMS_CORE_PACKAGE, onUser = DeviceState.UserType.ANY)
-@RequirePackageInstalled(value = PLAY_STORE_PACKAGE, onUser = DeviceState.UserType.ANY)
-@RequirePackageInstalled(value = GSF_PACKAGE, onUser = DeviceState.UserType.ANY)
-public @interface RequireGmsBuild {
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsInstrumentation.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsInstrumentation.java
deleted file mode 100644
index 44cbeff..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsInstrumentation.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should be run using GMS Instrumentation for certain versions.
- *
- * <p>This will apply {@link RequireSdkVersion} to ensure that on the given versions, this test
- * only runs when the instrumented package is `com.google.android.gms`. It will also skip the test
- * if it is run with gms instrumentation on a version which does not require it.
- *
- * <p>This allows for two test configurations to be set up, one which instruments GMS and one
- * which does not - and both pointed at the same test sources.
- *
- * <p>Your test configuration may be configured so that this test is only run on a device with the
- * given state. Otherwise, you can use {@link DeviceState} to ensure that the test is
- * not run when the sdk version is not correct.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface RequireGmsInstrumentation {
- int min() default 1;
- int max() default Integer.MAX_VALUE;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireLowRamDevice.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireLowRamDevice.java
deleted file mode 100644
index 257cc2f..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireLowRamDevice.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Annotation to indicate that a test requires a low ram device.
- *
- * <p>This can be enforced by using {@link DeviceState}.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface RequireLowRamDevice {
- String reason();
- FailureMode failureMode() default FailureMode.SKIP;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireNotLowRamDevice.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireNotLowRamDevice.java
deleted file mode 100644
index 8d90920..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireNotLowRamDevice.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Annotation to indicate that a test does not run on low ram devices.
- *
- * <p>This can be enforced by using {@link DeviceState}.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface RequireNotLowRamDevice {
- String reason();
- FailureMode failureMode() default FailureMode.SKIP;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageInstalled.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageInstalled.java
deleted file mode 100644
index e2f3196..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageInstalled.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Repeatable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run only when a given package is installed.
- *
- * <p>You can guarantee that these methods do not run on devices lacking the package by
- * using {@code DeviceState}.
- *
- * <p>By default the test will be skipped if the package is not available. If you'd rather the test
- * fail then use {@code failureMode = FailureMode.FAIL}.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@Repeatable(RequirePackagesInstalled.class)
-public @interface RequirePackageInstalled {
- String value();
- DeviceState.UserType onUser() default DeviceState.UserType.CURRENT_USER;
- FailureMode failureMode() default FailureMode.SKIP;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageNotInstalled.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageNotInstalled.java
deleted file mode 100644
index 36375c1..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageNotInstalled.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Repeatable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run only when a given package is not installed.
- *
- * <p>You can guarantee that these methods do not run on devices with the package by
- * using {@code DeviceState}.
- *
- * <p>By default the test will be skipped if the package is available. If you'd rather the test
- * fail then use {@code failureMode = FailureMode.FAIL}. If you'd like to uninstall the package if
- * it is installed, see {@link EnsurePackageNotInstalled}.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@Repeatable(RequirePackagesNotInstalled.class)
-public @interface RequirePackageNotInstalled {
- String value();
- DeviceState.UserType onUser() default DeviceState.UserType.CURRENT_USER;
- FailureMode failureMode() default FailureMode.SKIP;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunNotOnSecondaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunNotOnSecondaryUser.java
deleted file mode 100644
index 446bb85..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunNotOnSecondaryUser.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on a non-secondary user.
- *
- * <p>Your test configuration should be such that this test is only run where a non-secondary user
- * is created and the test is being run on that user.
- *
- * <p>Optionally, you can guarantee that these methods do not run on a secondary user by
- * using {@link DeviceState}.
- *
- * <p>This annotation by default opts a test into multi-user presubmit. New tests should also be
- * annotated {@link Postsubmit} until they are shown to meet the multi-user presubmit
- * requirements.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-// To ensure that the test doesn't run on the secondary user we require that the test run on
-// the primary user to ensure consistent behaviour.
-@RequireRunOnPrimaryUser
-public @interface RequireRunNotOnSecondaryUser {
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java
deleted file mode 100644
index a58eb28..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-import com.android.bedstead.harrier.annotations.meta.RequireRunOnUserAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on the primary user.
- *
- * <p>Your test configuration should be such that this test is only run on the primary user
- *
- * <p>Optionally, you can guarantee that these methods do not run outside of the primary
- * user by using {@link DeviceState}.
- *
- * <p>Note that in practice this requires that the test runs on the system user, but excludes
- * headless system users. To mark that a test should run on the system user, including headless
- * system users, see {@link RequireRunOnSystemUser}.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequireRunOnUserAnnotation("android.os.usertype.full.SYSTEM")
-public @interface RequireRunOnPrimaryUser {
- /**
- * Should we ensure that we are switched to the given user
- */
- OptionalBoolean switchedToUser() default TRUE;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default EARLY;
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java
deleted file mode 100644
index b3df118..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-import com.android.bedstead.harrier.annotations.meta.RequireRunOnUserAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on a secondary user.
- *
- * <p>Your test configuration should be such that this test is only run where a secondary user is
- * created and the test is being run on that user.
- *
- * <p>Optionally, you can guarantee that these methods do not run outside of a secondary user by
- * using {@link DeviceState}.
- *
- * <p>This annotation by default opts a test into multi-user presubmit. New tests should also be
- * annotated {@link Postsubmit} until they are shown to meet the multi-user presubmit
- * requirements.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequireRunOnUserAnnotation("android.os.usertype.full.SECONDARY")
-public @interface RequireRunOnSecondaryUser {
- /**
- * Should we ensure that we are switched to the given user
- */
- OptionalBoolean switchedToUser() default TRUE;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSystemUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSystemUser.java
deleted file mode 100644
index 52c927e..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSystemUser.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-import com.android.bedstead.harrier.annotations.meta.RequireRunOnUserAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on the system user.
- *
- * <p>Your test configuration should be such that this test is only run on the system user
- *
- * <p>Optionally, you can guarantee that these methods do not run outside of the system
- * user by using {@link DeviceState}.
- *
- * <p>Note that this requires that the test runs on the system user, including headless system
- * users. To mark that a test should run on the primary user, excluding headless
- * system users, see {@link RequireRunOnPrimaryUser}.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequireRunOnUserAnnotation(
- {"android.os.usertype.full.SYSTEM", "android.os.usertype.system.HEADLESS"})
-public @interface RequireRunOnSystemUser {
- /**
- * Should we ensure that we are switched to the given user
- */
- OptionalBoolean switchedToUser() default TRUE;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java
deleted file mode 100644
index c2daad9..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.OptionalBoolean.ANY;
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-import com.android.bedstead.harrier.annotations.meta.RequireRunOnProfileAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run within a Tv profile.
- *
- * <p>Your test configuration should be such that this test is only run where a Tv profile is
- * created and the test is being run within that user.
- *
- * <p>Optionally, you can guarantee that these methods do not run outside of a Tv
- * profile by using {@link DeviceState}.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequireRunOnProfileAnnotation("com.android.tv.profile")
-public @interface RequireRunOnTvProfile {
- OptionalBoolean installInstrumentedAppInParent() default ANY;
-
- /**
- * Should we ensure that we are switched to the parent of the profile.
- */
- OptionalBoolean switchedToParentUser() default TRUE;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default EARLY;
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java
deleted file mode 100644
index b06a909..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static android.content.pm.PackageManager.FEATURE_DEVICE_ADMIN;
-
-import static com.android.bedstead.harrier.OptionalBoolean.ANY;
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-import com.android.bedstead.harrier.annotations.enterprise.EnsureHasProfileOwner;
-import com.android.bedstead.harrier.annotations.meta.RequireRunOnProfileAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run within a work profile.
- *
- * <p>Your test configuration should be such that this test is only run where a work profile is
- * created and the test is being run within that user.
- *
- * <p>Optionally, you can guarantee that these methods do not run outside of a work
- * profile by using {@link DeviceState}.
- *
- * <p>This annotation by default opts a test into multi-user presubmit. New tests should also be
- * annotated {@link Postsubmit} until they are shown to meet the multi-user presubmit requirements.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequireRunOnProfileAnnotation(value = "android.os.usertype.profile.MANAGED", hasProfileOwner = true)
-@EnsureHasProfileOwner
-@RequireFeature(FEATURE_DEVICE_ADMIN)
-public @interface RequireRunOnWorkProfile {
- OptionalBoolean installInstrumentedAppInParent() default ANY;
-
- /**
- * Whether the profile owner's DPC should be returned by calls to {@link DeviceState#dpc()}.
- *
- * <p>Only one device policy controller per test should be marked as primary.
- */
- boolean dpcIsPrimary() default false;
-
- /**
- * Affiliation ids to be set for the profile owner.
- */
- String[] affiliationIds() default {};
-
- /**
- * Should we ensure that we are switched to the parent of the profile.
- */
- OptionalBoolean switchedToParentUser() default TRUE;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default EARLY;
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireSdkVersion.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireSdkVersion.java
deleted file mode 100644
index 8a272e9..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireSdkVersion.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should only run on specified sdk versions.
- *
- * <p>Your test configuration may be configured so that this test is only run on a device with the
- * given version. Otherwise, you can use {@link DeviceState} to ensure that the test is
- * not run when the sdk version is not correct.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface RequireSdkVersion {
- int min() default 1;
- int max() default Integer.MAX_VALUE;
- String reason() default "";
- FailureMode failureMode() default FailureMode.SKIP;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireUserSupported.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireUserSupported.java
deleted file mode 100644
index 325447c..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireUserSupported.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Repeatable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should only run if a particular user type is supported
- *
- * <p>Your test configuration may be configured so that this test is only run on a user which
- * supports the user types. Otherwise, you can use {@link DeviceState} to ensure that the test is
- * not run when the user type is not supported.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@Repeatable(RequireUsersSupported.class)
-public @interface RequireUserSupported {
- String value();
- FailureMode failureMode() default FailureMode.SKIP;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDelegate.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDelegate.java
deleted file mode 100644
index c581561..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDelegate.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.enterprise;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test requires that the given admin delegates the given scope to a test app.
- *
- * <p>You should use {@link DeviceState} to ensure that the device enters
- * the correct state for the method. You can use {@link DeviceState#delegate()} to interact with
- * the delegate.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface EnsureHasDelegate {
-
- int ENSURE_HAS_DELEGATE_WEIGHT = DO_PO_WEIGHT + 1; // Should run after setting DO/PO
-
- enum AdminType {
- DEVICE_OWNER,
- PROFILE_OWNER,
- PRIMARY
- }
-
- /**
- * The admin that should delegate this scope.
- *
- * <p>If this is set to {@link AdminType#PRIMARY} and {@link #isPrimary()} is true, then the
- * delegate will replace the primary dpc as primary without error.
- */
- AdminType admin();
-
- /** The scope being delegated. */
- String[] scopes();
-
- /**
- * Whether this delegate should be returned by calls to {@link DeviceState#policyManager()}.
- *
- * <p>Only one policy manager per test should be marked as primary.
- */
- boolean isPrimary() default false;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default ENSURE_HAS_DELEGATE_WEIGHT;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDeviceOwner.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDeviceOwner.java
deleted file mode 100644
index 2fdfc9d..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDeviceOwner.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.enterprise;
-
-import static android.content.pm.PackageManager.FEATURE_DEVICE_ADMIN;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-import com.android.bedstead.harrier.annotations.FailureMode;
-import com.android.bedstead.harrier.annotations.RequireFeature;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test requires that a device owner is available on the device.
- *
- * <p>Your test configuration may be configured so that this test is only run on a device which has
- * a device owner. Otherwise, you can use {@link DeviceState} to ensure that the device enters
- * the correct state for the method. If using {@link DeviceState}, you can use
- * {@link DeviceState#deviceOwner()} to interact with the device owner.
- *
- * <p>When running on a device with a headless system user, enforcing this with {@link DeviceState}
- * will also result in the profile owner of the current user being set to the same device policy
- * controller.
- *
- * <p>If {@link DeviceState} is required to set the device owner (because there isn't one already)
- * then all users and accounts may be removed from the device.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequireFeature(FEATURE_DEVICE_ADMIN)
-public @interface EnsureHasDeviceOwner {
-
- int DO_PO_WEIGHT = MIDDLE;
-
- /** Behaviour if the device owner cannot be set. */
- FailureMode failureMode() default FailureMode.FAIL;
-
- /**
- * Whether this DPC should be returned by calls to {@link DeviceState#dpc()} or
- * {@link DeviceState#policyManager()}}.
- *
- * <p>Only one policy manager per test should be marked as primary.
- */
- boolean isPrimary() default false;
-
- /**
- * Affiliation ids to be set for the device owner.
- */
- String[] affiliationIds() default {};
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default DO_PO_WEIGHT;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDelegate.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDelegate.java
deleted file mode 100644
index c0516f3..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDelegate.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.enterprise;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDelegate.ENSURE_HAS_DELEGATE_WEIGHT;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test requires that the given admin does not delegate the given scope to a test app.
- *
- * <p>You should use {@link DeviceState} to ensure that the device enters
- * the correct state for the method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface EnsureHasNoDelegate {
-
- enum AdminType {
- DEVICE_OWNER,
- PROFILE_OWNER,
- PRIMARY,
- ANY
- }
-
-
- /**
- * The admin that should not delegate this scope.
- *
- * <p>Defaults to any admin
- *
- * <p>If this is set to {@link AdminType#PRIMARY} and {@link #isPrimary()} is true, then the
- * delegate will replace the primary dpc as primary without error.
- */
- AdminType admin() default AdminType.ANY;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default ENSURE_HAS_DELEGATE_WEIGHT + 1; // Should run after setting delegate
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDeviceOwner.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDeviceOwner.java
deleted file mode 100644
index 307b554..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDeviceOwner.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.enterprise;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test requires that there is no device owner on the device.
- *
- * <p>Your test configuration may be configured so that this test is only run on a device which has
- * no device owner. Otherwise, you can use {@link DeviceState} to ensure that the device enters
- * the correct state for the method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface EnsureHasNoDeviceOwner {
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default DO_PO_WEIGHT;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDpc.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDpc.java
deleted file mode 100644
index c97fd7f..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDpc.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.enterprise;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test requires that there is no dpc on the device.
- *
- * <p>This checks that there is no device owner, the current user has no work profiles, and the
- * current user has no profile owner.
- *
- * <p>Your test configuration may be configured so that this test is only run on a device which has
- * no dpc. Otherwise, you can use {@link DeviceState} to ensure that the device enters
- * the correct state for the method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@EnsureHasNoDeviceOwner
-@EnsureHasNoWorkProfile
-@EnsureHasNoProfileOwner
-public @interface EnsureHasNoDpc {
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default DO_PO_WEIGHT;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoProfileOwner.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoProfileOwner.java
deleted file mode 100644
index 561da3c..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoProfileOwner.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.enterprise;
-
-import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test requires that there is no profile owner on the given user.
- *
- * <p>You can use {@link DeviceState} to ensure that the device enters the correct state for the
- * method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface EnsureHasNoProfileOwner {
- /** Which user type the profile owner should not be attached to. */
- DeviceState.UserType onUser() default CURRENT_USER;
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default DO_PO_WEIGHT;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasProfileOwner.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasProfileOwner.java
deleted file mode 100644
index a408a4d..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasProfileOwner.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.enterprise;
-
-import static android.content.pm.PackageManager.FEATURE_DEVICE_ADMIN;
-
-import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-import com.android.bedstead.harrier.annotations.RequireFeature;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test requires that a profile owner is set.
- *
- * <p>You can use {@link DeviceState} to ensure that the device enters
- * the correct state for the method. If using {@link DeviceState}, you can use
- * {@link DeviceState#profileOwner()} to interact with the profile owner.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequireFeature(FEATURE_DEVICE_ADMIN)
-public @interface EnsureHasProfileOwner {
- /** Which user type the work profile should be attached to. */
- DeviceState.UserType onUser() default CURRENT_USER;
-
- /**
- * Whether this DPC should be returned by calls to {@link DeviceState#dpc()} or
- * {@link DeviceState#policyManager()}}.
- *
- * <p>Only one policy manager per test should be marked as primary.
- */
- boolean isPrimary() default false;
-
- /**
- * Affiliation ids to be set for the profile owner.
- */
- String[] affiliationIds() default {};
-
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default DO_PO_WEIGHT;
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfile.java
deleted file mode 100644
index fdd78f0..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfile.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.meta;
-
-import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.Target;
-
-/**
- * This annotation should not be used directly. It exists as a template for annotations which
- * ensure that a given user does not have a specific profile type.
- */
-@Target({})
-public @interface EnsureHasNoProfile {
- DeviceState.UserType forUser() default CURRENT_USER;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfile.java
deleted file mode 100644
index b9b3e08..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfile.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.meta;
-
-import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-import static com.android.bedstead.harrier.OptionalBoolean.ANY;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-
-import java.lang.annotation.Target;
-
-/**
- * This annotation should not be used directly. It exists as a template for annotations which
- * ensure that a given user has a specific profile type.
- */
-@Target({})
-public @interface EnsureHasProfile {
- /** Which user type the profile should be attached to. */
- DeviceState.UserType forUser() default CURRENT_USER;
-
- /** Whether the test app should be installed in the profile. */
- boolean installTestApp() default true;
-
- /**
- * Whether the profile owner's DPC should be returned by calls to {@link DeviceState#dpc()}.
- *
- * <p>Only one device policy controller per test should be marked as primary.
- */
- // NOTE: This field is only required if hasProfileOwner=true
- boolean dpcIsPrimary() default false;
-
- /**
- * Should we ensure that we are switched to the parent of the profile.
- */
- OptionalBoolean switchedToParentUser() default ANY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfile.java
deleted file mode 100644
index 6bc4087..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfile.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.meta;
-
-import static com.android.bedstead.harrier.OptionalBoolean.ANY;
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-
-import java.lang.annotation.Target;
-
-/**
- * This annotation should not be used directly. It exists as a template for annotations which
- * ensure that a test runs on a given profile type.
- */
-@Target({})
-public @interface RequireRunOnProfile {
- OptionalBoolean installInstrumentedAppInParent() default ANY;
-
- /**
- * Whether the profile owner's DPC should be returned by calls to {@link DeviceState#dpc()}.
- *
- * <p>Only one device policy controller per test should be marked as primary.
- */
- // NOTE: This field is only required if hasProfileOwner=true
- boolean dpcIsPrimary() default false;
-
- /**
- * Affiliation ids to be set for the profile owner.
- */
- // NOTE: This field is only required if hasProfileOwner=true
- String[] affiliationIds() default {};
-
- /**
- * Should we ensure that we are switched to the parent of the profile.
- */
- OptionalBoolean switchedToParentUser() default TRUE;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerPrimaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerPrimaryUser.java
deleted file mode 100644
index 17b5240..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerPrimaryUser.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.parameterized;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.LATE;
-
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
-import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner;
-import com.android.bedstead.harrier.annotations.enterprise.EnsureHasProfileOwner;
-import com.android.bedstead.harrier.annotations.meta.ParameterizedAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Parameterize a test so that it runs on the primary user
- */
-@Target({ElementType.METHOD, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@ParameterizedAnnotation
-// Explicitly primary user which excludes headless users as this isn't a valid mode on headless
-@RequireRunOnPrimaryUser
-@EnsureHasNoDeviceOwner
-@EnsureHasProfileOwner(isPrimary = true)
-public @interface IncludeRunOnProfileOwnerPrimaryUser {
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default LATE;
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile.java
deleted file mode 100644
index 303e123..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.annotations.parameterized;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.LATE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
-import com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser;
-import com.android.bedstead.harrier.annotations.meta.ParameterizedAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Parameterize a test so that it runs on a device which has a primary and work profile, but runs
- * the test on a secondary user which is in a different profile group.
- */
-@Target({ElementType.METHOD, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@ParameterizedAnnotation
-@RequireRunOnSecondaryUser
-@EnsureHasWorkProfile(forUser = DeviceState.UserType.PRIMARY_USER, dpcIsPrimary = true)
-public @interface IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile {
- /**
- * Weight sets the order that annotations will be resolved.
- *
- * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
- *
- * <p>If there is an order requirement between annotations, ensure that the weight of the
- * annotation which must be resolved first is lower than the one which must be resolved later.
- *
- * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
- */
- int weight() default LATE;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/AccountManagement.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/AccountManagement.java
deleted file mode 100644
index 375ef7c..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/AccountManagement.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for account management tests.
- *
- * <p>This is used by {@link
- * DevicePolicyManager#setAccountManagementDisabled(ComponentName, String, boolean)} and
- * {@link DevicePolicyManager#getAccountTypesWithManagementDisabled()}.
- */
-@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER)
-public final class AccountManagement {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictions.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictions.java
deleted file mode 100644
index 9e30dad..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictions.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_IN_BACKGROUND;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.CAN_BE_DELEGATED;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.os.Bundle;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for application restrictions.
- *
- * <p>This is used by methods such as
- * {@link DevicePolicyManager#setApplicationRestrictions(ComponentName, String, Bundle)} and
- * {@link DevicePolicyManager#getApplicationRestrictions(ComponentName, String)}.
- */
-@EnterprisePolicy(
- dpc = {
- APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIES_IN_BACKGROUND | CAN_BE_DELEGATED,
- APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED},
- delegatedScopes = DELEGATION_APP_RESTRICTIONS
- )
-public final class ApplicationRestrictions {
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictionsManagingPackage.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictionsManagingPackage.java
deleted file mode 100644
index 379b071..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictionsManagingPackage.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_IN_BACKGROUND;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.os.Bundle;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for application restrictions.
- *
- * <p>This is used by the method
- * {@link DevicePolicyManager#setApplicationRestrictionsManagingPackage(ComponentName, String, Bundle)}
- */
-@EnterprisePolicy(
- dpc = {
- APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIES_IN_BACKGROUND,
- APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
-public final class ApplicationRestrictionsManagingPackage {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CaCertManagement.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CaCertManagement.java
deleted file mode 100644
index 4a6d384..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CaCertManagement.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policies around installing/uninstalling CaCerts
- *
- * <p>This is used by methods such as
- * {@link DevicePolicyManager#installCaCert(ComponentName, byte[])} and
- * {@link DevicePolicyManager#uninstallCaCert(ComponentName, byte[])}.
- */
-@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
-public final class CaCertManagement {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CameraPolicy.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CameraPolicy.java
deleted file mode 100644
index 4309473..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CameraPolicy.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_PROFILE;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_GLOBALLY;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for application restrictions.
- *
- * <p>This is used by methods such as
- * {@link DevicePolicyManager#setCameraDisabled(ComponentName, boolean)} and
- * {@link DevicePolicyManager#getCameraDisabled(ComponentName)}.
- */
-// TODO(b/201753989): Update the profileOwner flag once the behaviour of setCameraDisabled
-// is properly defined on secondary user POs.
-@EnterprisePolicy(dpc = {
- APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO | APPLIES_GLOBALLY,
- APPLIED_BY_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER
-})
-public class CameraPolicy {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CreateAndManageUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CreateAndManageUser.java
deleted file mode 100644
index c1c62c1..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CreateAndManageUser.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.os.PersistableBundle;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for create and manage user.
- *
- * <p>This is used by methods such as {@link DevicePolicyManager#createAndManageUser(
- * ComponentName, String, ComponentName, PersistableBundle, int)}.
- */
-@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER})
-public final class CreateAndManageUser {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/DefaultSmsApplication.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/DefaultSmsApplication.java
deleted file mode 100644
index c7d8f80..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/DefaultSmsApplication.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for set default SMS application test.
- *
- * <p>This is used by {@link DevicePolicyManager#setDefaultSmsApplication(ComponentName, String)}.
- */
-@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER)
-public final class DefaultSmsApplication {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/Delegation.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/Delegation.java
deleted file mode 100644
index 5bcde92..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/Delegation.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-import java.util.List;
-
-/**
- * Policy for general admin delegation that doesn't have additional scope-specific constraints on
- * the admin type. Specific delegations with these constraints have their own policies.
- *
- * <p>This is used for methods such as {@link
- * DevicePolicyManager#setDelegatedScopes(ComponentName, String, List)}.
- */
-@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
-public final class Delegation {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/EnrollmentSpecificId.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/EnrollmentSpecificId.java
deleted file mode 100644
index 2311c5e..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/EnrollmentSpecificId.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for testing enrollment specific ID.
- * See {@link DevicePolicyManager#getEnrollmentSpecificId()} for more detail.
- */
-@EnterprisePolicy(dpc = {
- APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER,
- APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER
-})
-public final class EnrollmentSpecificId {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/KeyManagement.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/KeyManagement.java
deleted file mode 100644
index 9d409e6..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/KeyManagement.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-import java.security.PrivateKey;
-import java.security.cert.Certificate;
-
-/**
- * Policies around key management
- *
- * <p>This is used by methods such as
- * {@link DevicePolicyManager#installKeyPair(ComponentName, PrivateKey, Certificate, String)} and
- * {@link DevicePolicyManager#removeKeyPair(ComponentName, String)}.
- */
-@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER,
- APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
-public final class KeyManagement {
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/LockTask.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/LockTask.java
deleted file mode 100644
index a4e8124..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/LockTask.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_AFFILIATED_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policies around Lock Task mode
- * (https://developer.android.com/work/dpc/dedicated-devices/lock-task-mode).
- *
- * <p>This is used by methods such as
- * {@link DevicePolicyManager#setLockTaskFeatures(ComponentName, int)} and
- * {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])}.
- */
-@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER,
- APPLIED_BY_AFFILIATED_PROFILE_OWNER | APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO | APPLIES_TO_OWN_USER})
-public final class LockTask {
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/NetworkLoggingDelegation.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/NetworkLoggingDelegation.java
deleted file mode 100644
index ae4dfcd..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/NetworkLoggingDelegation.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_PROFILE;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-import java.util.List;
-
-/**
- * Policy for network logging delegation.
- *
- * <p>This is used for methods such as {@link
- * DevicePolicyManager#setDelegatedScopes(ComponentName, String, List)} with scope {@link
- * DevicePolicyManager#DELEGATION_NETWORK_LOGGING}.
- */
-@EnterprisePolicy(dpc = {
- APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER})
-public final class NetworkLoggingDelegation {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/PreferentialNetworkService.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/PreferentialNetworkService.java
deleted file mode 100644
index 45185a9..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/PreferentialNetworkService.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_PROFILE;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for testing preferential network service.
- * See {@link DevicePolicyManager#setPreferentialNetworkServiceEnabled(boolean)} for more detail.
- */
-@EnterprisePolicy(dpc = APPLIED_BY_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER)
-public final class PreferentialNetworkService {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ResetPasswordWithToken.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ResetPasswordWithToken.java
deleted file mode 100644
index 82a30a8..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ResetPasswordWithToken.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policies around resetting a new device password
- *
- * <p>This is used by methods such as
- * {@link DevicePolicyManager#resetPasswordWithToken(ComponentName, String, byte[], int)}
- */
-@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER,
- APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
-public class ResetPasswordWithToken {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java
deleted file mode 100644
index e165663..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PARENT_INSTANCE_OF_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for disabling screen capture.
- *
- * <p>Users of this policy are
- * {@link DevicePolicyManager#setScreenCaptureDisabled(ComponentName, boolean)}.
- */
-@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER
- | APPLIED_BY_PARENT_INSTANCE_OF_PROFILE_OWNER | APPLIES_TO_OWN_USER)
-public final class ScreenCaptureDisabled {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SecurityLoggingDelegation.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SecurityLoggingDelegation.java
deleted file mode 100644
index b8f7617..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SecurityLoggingDelegation.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-import java.util.List;
-
-/**
- * Policy for security logging delegation.
- *
- * <p>This is used for methods such as {@link
- * DevicePolicyManager#setDelegatedScopes(ComponentName, String, List)} with scope {@link
- * DevicePolicyManager#DELEGATION_SECURITY_LOGGING}.
- */
-// TODO(b/198774281): COPE profile POs can call this too, but we need to add the flag.
-@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER})
-public final class SecurityLoggingDelegation {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetPermissionGrantState.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetPermissionGrantState.java
deleted file mode 100644
index bc3861a..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetPermissionGrantState.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.CAN_BE_DELEGATED;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policies around setting the grant state of a basic permission.
- *
- * <p>This is used by
- * {@link DevicePolicyManager#setPermissionGrantState(ComponentName, String, String, int)} when
- * granting permissions not covered by other policies.
- */
-@EnterprisePolicy(
- dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED,
- delegatedScopes = DELEGATION_PERMISSION_GRANT)
-public final class SetPermissionGrantState {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetSensorPermissionGranted.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetSensorPermissionGranted.java
deleted file mode 100644
index 9e1e832..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetSensorPermissionGranted.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policies around setting the grant state of a sensor permission.
- *
- * <p>This is used by
- * {@link DevicePolicyManager#setPermissionGrantState(ComponentName, String, String, int)} when
- * granting sensor permissions.
- */
-@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER)
-public final class SetSensorPermissionGranted {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetSmsPermissionGranted.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetSmsPermissionGranted.java
deleted file mode 100644
index fac7dcd..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetSmsPermissionGranted.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.CAN_BE_DELEGATED;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policies around setting the grant state of a SMS related permission.
- *
- * <p>This is used by
- * {@link DevicePolicyManager#setPermissionGrantState(ComponentName, String, String, int)} when
- * granting sms-related permissions.
- */
-// TODO(198311372): Check if APPLIED_BY_PROFILE_OWNER_USER is expected
-@EnterprisePolicy(
- dpc = APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIED_BY_PROFILE_OWNER_USER | CAN_BE_DELEGATED,
- delegatedScopes = DELEGATION_PERMISSION_GRANT)
-public final class SetSmsPermissionGranted {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SupportMessage.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SupportMessage.java
deleted file mode 100644
index b963290..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SupportMessage.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for long and short support messages.
- *
- * <p>Users of this policy are {@link DevicePolicyManager#setLongSupportMessage(ComponentName,
- * CharSequence)}, {@link DevicePolicyManager#setShortSupportMessage(ComponentName, CharSequence)},
- * {@link DevicePolicyManager#getLongSupportMessage(ComponentName)} and {@link
- * DevicePolicyManager#getShortSupportMessage(ComponentName)}.
- */
-@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER)
-public final class SupportMessage {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/UserControlDisabledPackages.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/UserControlDisabledPackages.java
deleted file mode 100644
index 2adaf92..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/UserControlDisabledPackages.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_GLOBALLY;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-import java.util.List;
-
-/**
- * Policy for user control disabled packages.
- *
- * <p>This is used by methods such as
- * {@link DevicePolicyManager#setUserControlDisabledPackages(ComponentName, List)} and
- * {@link DevicePolicyManager#getUserControlDisabledPackages(ComponentName)}.
- */
-@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIES_GLOBALLY)
-public final class UserControlDisabledPackages {
-}
diff --git a/common/device-side/bedstead/harrier/src/test/java/com/android/bedstead/harrier/BedsteadJUnit4Test.java b/common/device-side/bedstead/harrier/src/test/java/com/android/bedstead/harrier/BedsteadJUnit4Test.java
new file mode 100644
index 0000000..ce008dd
--- /dev/null
+++ b/common/device-side/bedstead/harrier/src/test/java/com/android/bedstead/harrier/BedsteadJUnit4Test.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.harrier;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.bedstead.harrier.annotations.AfterClass;
+import com.android.bedstead.harrier.annotations.IntTestParameter;
+import com.android.bedstead.harrier.annotations.StringTestParameter;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnDeviceOwnerUser;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnProfileOwnerPrimaryUser;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@RunWith(BedsteadJUnit4.class)
+public class BedsteadJUnit4Test {
+
+ @ClassRule @Rule
+ public static final DeviceState sDeviceState = new DeviceState();
+
+ @StringTestParameter({"A", "B"})
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface TwoValuesStringTestParameter {
+
+ }
+
+ private static int sSimpleParameterizedCalls = 0;
+ private static int sMultipleSimpleParameterizedCalls = 0;
+ private static int sBedsteadParameterizedCalls = 0;
+ private static int sBedsteadPlusSimpleParameterizedCalls = 0;
+ private static int sIndirectParameterizedCalls = 0;
+ private static int sIntParameterizedCalls = 0;
+
+ @AfterClass
+ public static void afterClass() {
+ assertThat(sSimpleParameterizedCalls).isEqualTo(2);
+ assertThat(sMultipleSimpleParameterizedCalls).isEqualTo(4);
+ assertThat(sBedsteadParameterizedCalls).isEqualTo(2);
+ assertThat(sBedsteadPlusSimpleParameterizedCalls).isEqualTo(4);
+ assertThat(sIndirectParameterizedCalls).isEqualTo(2);
+ assertThat(sIntParameterizedCalls).isEqualTo(2);
+ }
+
+ @Test
+ @IncludeRunOnDeviceOwnerUser
+ @IncludeRunOnProfileOwnerPrimaryUser
+ public void bedsteadParameterized() {
+ sBedsteadParameterizedCalls += 1;
+ }
+
+ @Test
+ @IncludeRunOnDeviceOwnerUser
+ @IncludeRunOnProfileOwnerPrimaryUser
+ public void bedsteadPlusSimpleParameterized(@StringTestParameter({"A", "B"}) String argument) {
+ sBedsteadPlusSimpleParameterizedCalls += 1;
+ }
+
+ @Test
+ public void simpleParameterized(@StringTestParameter({"A", "B"}) String argument) {
+ sSimpleParameterizedCalls += 1;
+ }
+
+ @Test
+ public void multipleSimpleParameterized(
+ @StringTestParameter({"A", "B"}) String argument1,
+ @StringTestParameter({"C", "D"}) String argument2) {
+ sMultipleSimpleParameterizedCalls += 1;
+ }
+
+ @Test
+ public void indirectParameterized(@TwoValuesStringTestParameter String argument) {
+ sIndirectParameterizedCalls += 1;
+ }
+
+ @Test
+ public void intParameterized(@IntTestParameter({1, 2}) int argument) {
+ sIntParameterizedCalls += 1;
+ }
+}
diff --git a/common/device-side/bedstead/harrier/src/test/java/com/android/bedstead/harrier/DeviceStateTest.java b/common/device-side/bedstead/harrier/src/test/java/com/android/bedstead/harrier/DeviceStateTest.java
index ba3c2ef..a82f7db 100644
--- a/common/device-side/bedstead/harrier/src/test/java/com/android/bedstead/harrier/DeviceStateTest.java
+++ b/common/device-side/bedstead/harrier/src/test/java/com/android/bedstead/harrier/DeviceStateTest.java
@@ -23,10 +23,10 @@
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static com.android.bedstead.harrier.DeviceState.UserType.ANY;
-import static com.android.bedstead.harrier.DeviceState.UserType.PRIMARY_USER;
import static com.android.bedstead.harrier.OptionalBoolean.FALSE;
import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.UserType.ANY;
+import static com.android.bedstead.harrier.UserType.PRIMARY_USER;
import static com.android.bedstead.harrier.annotations.RequireAospBuild.GMS_CORE_PACKAGE;
import static com.android.bedstead.harrier.annotations.RequireCnGmsBuild.CHINA_GOOGLE_SERVICES_FEATURE;
import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDelegate.AdminType.DEVICE_OWNER;
@@ -40,6 +40,7 @@
import static org.testng.Assert.assertThrows;
import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
import android.os.Build;
import android.os.Bundle;
import android.platform.test.annotations.AppModeFull;
@@ -53,6 +54,8 @@
import com.android.bedstead.harrier.annotations.EnsureHasTvProfile;
import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
import com.android.bedstead.harrier.annotations.EnsurePackageNotInstalled;
+import com.android.bedstead.harrier.annotations.EnsurePasswordNotSet;
+import com.android.bedstead.harrier.annotations.EnsureScreenIsOn;
import com.android.bedstead.harrier.annotations.RequireAospBuild;
import com.android.bedstead.harrier.annotations.RequireCnGmsBuild;
import com.android.bedstead.harrier.annotations.RequireDoesNotHaveFeature;
@@ -82,6 +85,7 @@
import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnBackgroundDeviceOwnerUser;
import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnDeviceOwnerUser;
import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnParentOfProfileOwnerUsingParentInstance;
import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner;
import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnProfileOwnerProfileWithNoDeviceOwner;
import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile;
@@ -110,6 +114,11 @@
private static final String TEST_PERMISSION_1 = INTERACT_ACROSS_PROFILES;
private static final String TEST_PERMISSION_2 = INTERACT_ACROSS_USERS_FULL;
+ private static final DevicePolicyManager sLocalDevicePolicyManager =
+ TestApis.context().instrumentedContext().getSystemService(DevicePolicyManager.class);
+
+ private static final long TIMEOUT = 4000000;
+
@Test
@EnsureHasWorkProfile
public void workProfile_workProfileProvided_returnsWorkProfile() {
@@ -359,14 +368,14 @@
}
@EnsureHasSecondaryUser
- @EnsureHasProfileOwner(onUser = DeviceState.UserType.SECONDARY_USER)
+ @EnsureHasProfileOwner(onUser = UserType.SECONDARY_USER)
public void ensureHasProfileOwnerAnnotation_otherUser_setsProfileOwner() {
assertThat(TestApis.devicePolicy().getProfileOwner(sDeviceState.secondaryUser()))
.isNotNull();
}
@EnsureHasSecondaryUser
- @EnsureHasNoProfileOwner(onUser = DeviceState.UserType.SECONDARY_USER)
+ @EnsureHasNoProfileOwner(onUser = UserType.SECONDARY_USER)
public void ensureHasNoProfileOwnerAnnotation_otherUser_profileOwnerIsNotSet() {
assertThat(TestApis.devicePolicy().getProfileOwner(sDeviceState.secondaryUser())).isNull();
}
@@ -385,14 +394,14 @@
}
@EnsureHasSecondaryUser
- @EnsureHasProfileOwner(onUser = DeviceState.UserType.SECONDARY_USER)
+ @EnsureHasProfileOwner(onUser = UserType.SECONDARY_USER)
public void profileOwner_otherUser_profileOwnerIsSet_returnsProfileOwner() {
assertThat(sDeviceState.profileOwner(sDeviceState.secondaryUser())).isNotNull();
}
@Test
@EnsureHasSecondaryUser
- @EnsureHasNoProfileOwner(onUser = DeviceState.UserType.SECONDARY_USER)
+ @EnsureHasNoProfileOwner(onUser = UserType.SECONDARY_USER)
public void profileOwner_otherUser_profileOwnerIsNotSet_throwsException() {
assertThrows(IllegalStateException.class, sDeviceState::profileOwner);
}
@@ -400,7 +409,7 @@
@Test
public void profileOwner_userType_onUserIsNull_throwsException() {
assertThrows(NullPointerException.class,
- () -> sDeviceState.profileOwner((DeviceState.UserType) null));
+ () -> sDeviceState.profileOwner((UserType) null));
}
@Test
@@ -775,4 +784,46 @@
public void ensureHasDelegateAnnotation_primaryAdminAndReplace_dpcReturnsDelegate() {
assertThat(sDeviceState.dpc()).isInstanceOf(RemoteDelegate.class);
}
+
+ @Test
+ @EnsureScreenIsOn
+ public void ensureScreenIsOnAnnotation_screenIsOn() {
+ assertThat(TestApis.device().isScreenOn()).isTrue();
+ }
+
+ @Test
+ @EnsurePasswordNotSet
+ public void requirePasswordNotSetAnnotation_passwordNotSet() {
+ assertThat(TestApis.users().instrumented().hasPassword()).isFalse();
+ }
+
+ @Test
+ @IncludeRunOnParentOfProfileOwnerUsingParentInstance
+ public void includeRunOnParentOfProfileOwnerUsingProfileInstanceAnnotation_runningOnParentOfProfile() {
+ assertThat(sDeviceState.workProfile().parent()).isEqualTo(TestApis.users().instrumented());
+ }
+
+ @Test
+ @IncludeRunOnParentOfProfileOwnerUsingParentInstance
+ public void includeRunOnParentOfProfileOwnerUsingProfileInstanceAnnotation_dpcIsOnProfile() {
+ assertThat(sDeviceState.dpc().user()).isEqualTo(sDeviceState.workProfile());
+ }
+
+ @Test
+ @IncludeRunOnParentOfProfileOwnerUsingParentInstance
+ public void includeRunOnParentOfProfileOwnerUsingProfileInstanceAnnotation_devicePolicyManagerAffectsParent() {
+ long previousRequiredStrongAuthTimeout =
+ sLocalDevicePolicyManager.getRequiredStrongAuthTimeout(/* admin= */ null);
+ try {
+ sDeviceState.dpc().devicePolicyManager()
+ .setRequiredStrongAuthTimeout(sDeviceState.dpc().componentName(), TIMEOUT);
+
+ assertThat(sLocalDevicePolicyManager
+ .getRequiredStrongAuthTimeout(/* admin= */ null)).isEqualTo(TIMEOUT);
+ } finally {
+ sDeviceState.dpc().devicePolicyManager()
+ .setRequiredStrongAuthTimeout(
+ sDeviceState.dpc().componentName(), previousRequiredStrongAuthTimeout);
+ }
+ }
}
diff --git a/common/device-side/bedstead/metricsrecorder/src/main/java/com/android/bedstead/metricsrecorder/EnterpriseMetricInfo.java b/common/device-side/bedstead/metricsrecorder/src/main/java/com/android/bedstead/metricsrecorder/EnterpriseMetricInfo.java
index 5584ed4..46cc3fb 100644
--- a/common/device-side/bedstead/metricsrecorder/src/main/java/com/android/bedstead/metricsrecorder/EnterpriseMetricInfo.java
+++ b/common/device-side/bedstead/metricsrecorder/src/main/java/com/android/bedstead/metricsrecorder/EnterpriseMetricInfo.java
@@ -27,6 +27,7 @@
private final int mType;
private final boolean mBoolean;
private final List<String> mStrings;
+ private final int mInteger;
EnterpriseMetricInfo(AtomsProto.DevicePolicyEvent event) {
mAdminPackageName = event.adminPackageName;
@@ -34,24 +35,34 @@
mBoolean = event.booleanValue;
mStrings = (event.stringListValue == null) ? new ArrayList<>() : Arrays.asList(
event.stringListValue.stringValue);
+ mInteger = event.integerValue;
}
+ /** Admin package name. */
public String adminPackageName() {
return mAdminPackageName;
}
+ /** Type of metric. */
public int type() {
return mType;
}
+ /** Arbitrary boolean value. */
public boolean Boolean() {
return mBoolean;
}
+ /** Arbitrary list of strings. */
public List<String> strings() {
return mStrings;
}
+ /** Arbitrary integer value. */
+ public int integer() {
+ return mInteger;
+ }
+
@Override
public String toString() {
return "EnterpriseMetricInfo{"
@@ -59,6 +70,7 @@
+ ", type=" + mType
+ ", boolean=" + mBoolean
+ ", strings=" + mStrings
+ + ", integer=" + mInteger
+ "}";
}
}
diff --git a/common/device-side/bedstead/metricsrecorder/src/main/java/com/android/bedstead/metricsrecorder/MetricQueryBuilder.java b/common/device-side/bedstead/metricsrecorder/src/main/java/com/android/bedstead/metricsrecorder/MetricQueryBuilder.java
index 215a513..0b9c1dd 100644
--- a/common/device-side/bedstead/metricsrecorder/src/main/java/com/android/bedstead/metricsrecorder/MetricQueryBuilder.java
+++ b/common/device-side/bedstead/metricsrecorder/src/main/java/com/android/bedstead/metricsrecorder/MetricQueryBuilder.java
@@ -43,11 +43,14 @@
new BooleanQueryHelper<>(this);
private final ListQueryHelper<MetricQueryBuilder, String, StringQuery<?>> mStringsQuery =
new ListQueryHelper<>(this);
+ private final IntegerQueryHelper<MetricQueryBuilder> mIntegerQuery =
+ new IntegerQueryHelper<>(this);
MetricQueryBuilder(EnterpriseMetricsRecorder recorder) {
mRecorder = recorder;
}
+ /** Query for {@link EnterpriseMetricInfo#type()}. */
public IntegerQuery<MetricQueryBuilder> whereType() {
if (hasStartedFetchingResults) {
throw new IllegalStateException("Cannot modify query after fetching results");
@@ -55,6 +58,7 @@
return mTypeQuery;
}
+ /** Query for {@link EnterpriseMetricInfo#adminPackageName()}. */
public StringQuery<MetricQueryBuilder> whereAdminPackageName() {
if (hasStartedFetchingResults) {
throw new IllegalStateException("Cannot modify query after fetching results");
@@ -62,6 +66,7 @@
return mAdminPackageNameQuery;
}
+ /** Query for {@link EnterpriseMetricInfo#Boolean()}. */
public BooleanQuery<MetricQueryBuilder> whereBoolean() {
if (hasStartedFetchingResults) {
throw new IllegalStateException("Cannot modify query after fetching results");
@@ -69,6 +74,14 @@
return mBooleanQuery;
}
+ /** Query for {@link EnterpriseMetricInfo#integer()}. */
+ public IntegerQuery<MetricQueryBuilder> whereInteger() {
+ if (hasStartedFetchingResults) {
+ throw new IllegalStateException("Cannot modify query after fetching results");
+ }
+ return mIntegerQuery;
+ }
+
public ListQueryHelper<MetricQueryBuilder, String, StringQuery<?>> whereStrings() {
if (hasStartedFetchingResults) {
throw new IllegalStateException("Cannot modify query after fetching results");
@@ -134,7 +147,8 @@
return mAdminPackageNameQuery.matches(metric.adminPackageName())
&& mTypeQuery.matches(metric.type())
&& mBooleanQuery.matches(metric.Boolean())
- && mStringsQuery.matches(metric.strings());
+ && mStringsQuery.matches(metric.strings())
+ && mIntegerQuery.matches(metric.integer());
}
@Override
diff --git a/common/device-side/bedstead/nene/Android.bp b/common/device-side/bedstead/nene/Android.bp
index 21809bb..7ca45b7 100644
--- a/common/device-side/bedstead/nene/Android.bp
+++ b/common/device-side/bedstead/nene/Android.bp
@@ -2,6 +2,22 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+java_library_host {
+ name: "NeneCommon",
+ srcs: [
+ "common/src/main/java/**/*.java"
+ ],
+}
+
+android_library {
+ name: "NeneCommonAndroid",
+ srcs: [
+ "common/src/main/java/**/*.java"
+ ],
+ manifest: "src/main/AndroidManifestInternal.xml",
+ min_sdk_version: "27"
+}
+
// TODO(b/203507664): Remove NeneInternal once we no longer need QUERY_ALL_PACKAGES to install
android_library {
name: "NeneInternal",
@@ -14,6 +30,7 @@
"compatibility-device-util-axt",
"guava",
"Queryable",
+ "NeneCommonAndroid",
"RemoteFrameworkClasses"
],
min_sdk_version: "27"
@@ -30,6 +47,7 @@
"compatibility-device-util-axt",
"guava",
"Queryable",
+ "NeneCommonAndroid",
"RemoteFrameworkClasses"
],
min_sdk_version: "27"
diff --git a/common/device-side/bedstead/nene/AndroidTest.xml b/common/device-side/bedstead/nene/AndroidTest.xml
index f7a5151..c5601fa 100644
--- a/common/device-side/bedstead/nene/AndroidTest.xml
+++ b/common/device-side/bedstead/nene/AndroidTest.xml
@@ -15,6 +15,8 @@
~ limitations under the License.
-->
<configuration description="Config for Nene test cases">
+ <option name="config-descriptor:metadata" key="parameter" value="multiuser" />
+ <target_preparer class="com.android.tradefed.targetprep.RunOnSystemUserTargetPreparer"/>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="NeneTest.apk" />
@@ -24,6 +26,8 @@
<option name="push" value="NeneTestApp1.apk->/data/local/tmp/NeneTestApp1.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
<option name="package" value="com.android.bedstead.nene.test" />
</test>
</configuration>
\ No newline at end of file
diff --git a/common/device-side/bedstead/nene/common/AndroidManifest.xml b/common/device-side/bedstead/nene/common/AndroidManifest.xml
new file mode 100644
index 0000000..3ba12e1
--- /dev/null
+++ b/common/device-side/bedstead/nene/common/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.bedstead.nene.common">
+ <uses-sdk android:minSdkVersion="27" />
+ <application>
+ </application>
+</manifest>
diff --git a/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/annotations/Nullable.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/annotations/Nullable.java
new file mode 100644
index 0000000..fb3ce77
--- /dev/null
+++ b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/annotations/Nullable.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.nene.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a value may be Null.
+ *
+ * <p>This can be used on host or device side code.
+ */
+@Target({ElementType.LOCAL_VARIABLE, ElementType.FIELD, ElementType.PARAMETER})
+@Retention(RetentionPolicy.SOURCE)
+public @interface Nullable {
+}
diff --git a/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/devicepolicy/CommonDevicePolicy.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/devicepolicy/CommonDevicePolicy.java
new file mode 100644
index 0000000..5927775
--- /dev/null
+++ b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/devicepolicy/CommonDevicePolicy.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.nene.devicepolicy;
+
+/** Device Policy helper methods common to host and device. */
+public class CommonDevicePolicy {
+ CommonDevicePolicy() {
+
+ }
+
+ /** See {@code DevicePolicyManager#DELEGATION_CERT_INSTALL}. */
+ public static final String DELEGATION_CERT_INSTALL = "delegation-cert-install";
+
+ /** See {@code DevicePolicyManager#DELEGATION_APP_RESTRICTIONS}. */
+ public static final String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
+
+ /** See {@code DevicePolicyManager#DELEGATION_BLOCK_UNINSTALL}. */
+ public static final String DELEGATION_BLOCK_UNINSTALL = "delegation-block-uninstall";
+
+ /** See {@code DevicePolicyManager#DELEGATION_PERMISSION_GRANT}. */
+ public static final String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant";
+
+ /** See {@code DevicePolicyManager#DELEGATION_PACKAGE_ACCESS}. */
+ public static final String DELEGATION_PACKAGE_ACCESS = "delegation-package-access";
+
+ /** See {@code DevicePolicyManager#DELEGATION_ENABLE_SYSTEM_APP}. */
+ public static final String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app";
+
+ /** See {@code DevicePolicyManager#DELEGATION_INSTALL_EXISTING_PACKAGE}. */
+ public static final String DELEGATION_INSTALL_EXISTING_PACKAGE =
+ "delegation-install-existing-package";
+
+ /** See {@code DevicePolicyManager#DELEGATION_KEEP_UNINSTALLED_PACKAGES}. */
+ public static final String DELEGATION_KEEP_UNINSTALLED_PACKAGES =
+ "delegation-keep-uninstalled-packages";
+
+ /** See {@code DevicePolicyManager#DELEGATION_NETWORK_LOGGING}. */
+ public static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
+
+ /** See {@code DevicePolicyManager#DELEGATION_CERT_SELECTION}. */
+ public static final String DELEGATION_CERT_SELECTION = "delegation-cert-selection";
+
+ /** See {@code DevicePolicyManager#DELEGATION_SECURITY_LOGGING}. */
+ public static final String DELEGATION_SECURITY_LOGGING = "delegation-security-logging";
+}
diff --git a/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java
new file mode 100644
index 0000000..a3845f9
--- /dev/null
+++ b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.nene.exceptions;
+
+import com.android.bedstead.nene.annotations.Nullable;
+
+/**
+ * An exception that gets thrown when interacting with Adb.
+ */
+public class AdbException extends Exception {
+
+ private final String mCommand;
+ private final @Nullable String mOutput;
+ private final @Nullable String mErr;
+
+ public AdbException(String message, String command, String output) {
+ this(message, command, output, /* err= */ (String) null);
+ }
+
+ public AdbException(String message, String command, String output, String err) {
+ super(message);
+ if (command == null) {
+ throw new NullPointerException();
+ }
+ this.mCommand = command;
+ this.mOutput = output;
+ this.mErr = err;
+ }
+
+ public AdbException(String message, String command, Throwable cause) {
+ this(message, command, /* output= */ null, cause);
+ }
+
+ public AdbException(String message, String command, String output, Throwable cause) {
+ super(message, cause);
+ if (command == null) {
+ throw new NullPointerException();
+ }
+ this.mCommand = command;
+ this.mOutput = output;
+ this.mErr = null;
+ }
+
+ public String command() {
+ return mCommand;
+ }
+
+ public String output() {
+ return mOutput;
+ }
+
+ public String error() {
+ return mErr;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder(super.toString());
+
+ stringBuilder.append("[command=\"").append(mCommand).append("\"");
+ if (mOutput != null) {
+ stringBuilder.append(", output=\"").append(mOutput).append("\"");
+ }
+ if (mErr != null) {
+ stringBuilder.append(", err=\"").append(mErr).append("\"");
+ }
+ stringBuilder.append("]");
+
+ return stringBuilder.toString();
+ }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbParseException.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/AdbParseException.java
similarity index 100%
rename from common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbParseException.java
rename to common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/AdbParseException.java
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/NeneException.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/NeneException.java
similarity index 100%
rename from common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/NeneException.java
rename to common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/NeneException.java
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/PollValueFailedException.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/PollValueFailedException.java
similarity index 100%
rename from common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/PollValueFailedException.java
rename to common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/PollValueFailedException.java
diff --git a/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/packages/CommonPackages.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/packages/CommonPackages.java
new file mode 100644
index 0000000..6a6f622
--- /dev/null
+++ b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/packages/CommonPackages.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.nene.packages;
+
+/** Packages helper methods common to host and device. */
+public class CommonPackages {
+ CommonPackages() {}
+
+ /** See {@code PackageManager#FEATURE_AUDIO_LOW_LATENCY}. */
+ public static final String FEATURE_AUDIO_LOW_LATENCY = "android.hardware.audio.low_latency";
+
+ /** See {@code PackageManager#FEATURE_AUDIO_OUTPUT}. */
+ public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
+
+ /** See {@code PackageManager#FEATURE_AUDIO_PRO}. */
+ public static final String FEATURE_AUDIO_PRO = "android.hardware.audio.pro";
+
+ /** See {@code PackageManager#FEATURE_BLUETOOTH}. */
+ public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
+
+ /** See {@code PackageManager#FEATURE_BLUETOOTH_LE}. */
+ public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
+
+ /** See {@code PackageManager#FEATURE_CAMERA}. */
+ public static final String FEATURE_CAMERA = "android.hardware.camera";
+
+ /** See {@code PackageManager#FEATURE_CAMERA_AUTOFOCUS}. */
+ public static final String FEATURE_CAMERA_AUTOFOCUS = "android.hardware.camera.autofocus";
+
+ /** See {@code PackageManager#FEATURE_CAMERA_ANY}. */
+ public static final String FEATURE_CAMERA_ANY = "android.hardware.camera.any";
+
+ /** See {@code PackageManager#FEATURE_CAMERA_EXTERNAL}. */
+ public static final String FEATURE_CAMERA_EXTERNAL = "android.hardware.camera.external";
+
+ /** See {@code PackageManager#FEATURE_CAMERA_FLASH}. */
+ public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
+
+ /** See {@code PackageManager#FEATURE_CAMERA_FRONT}. */
+ public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
+
+ /** See {@code PackageManager#FEATURE_CAMERA_LEVEL_FULL}. */
+ public static final String FEATURE_CAMERA_LEVEL_FULL = "android.hardware.camera.level.full";
+
+ /** See {@code PackageManager#FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR}. */
+ public static final String FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR =
+ "android.hardware.camera.capability.manual_sensor";
+
+ /** See {@code PackageManager#FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING}. */
+ public static final String FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING =
+ "android.hardware.camera.capability.manual_post_processing";
+
+ /** See {@code PackageManager#FEATURE_CAMERA_CAPABILITY_RAW}. */
+ public static final String FEATURE_CAMERA_CAPABILITY_RAW =
+ "android.hardware.camera.capability.raw";
+
+ /** See {@code PackageManager#FEATURE_CAMERA_AR}. */
+ public static final String FEATURE_CAMERA_AR =
+ "android.hardware.camera.ar";
+
+ /** See {@code PackageManager#FEATURE_CAMERA_CONCURRENT}. */
+ public static final String FEATURE_CAMERA_CONCURRENT = "android.hardware.camera.concurrent";
+
+ /** See {@code PackageManager#FEATURE_CONSUMER_IR}. */
+ public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
+
+ /** See {@code PackageManager#FEATURE_CONTEXT_HUB}. */
+ public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub";
+
+ /** See {@code PackageManager#FEATURE_CTS}. */
+ public static final String FEATURE_CTS = "android.software.cts";
+
+ /** See {@code PackageManager#FEATURE_CAR_TEMPLATES_HOST}. */
+ public static final String FEATURE_CAR_TEMPLATES_HOST =
+ "android.software.car.templates_host";
+
+ /** See {@code PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE}. */
+ public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE =
+ "android.hardware.identity_credential";
+
+ /** See {@code PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS}. */
+ public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS =
+ "android.hardware.identity_credential_direct_access";
+
+ /** See {@code PackageManager#FEATURE_LOCATION}. */
+ public static final String FEATURE_LOCATION = "android.hardware.location";
+
+ /** See {@code PackageManager#FEATURE_LOCATION_GPS}. */
+ public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
+
+ /** See {@code PackageManager#FEATURE_LOCATION_NETWORK}. */
+ public static final String FEATURE_LOCATION_NETWORK = "android.hardware.location.network";
+
+ /** See {@code PackageManager#FEATURE_FELICA}. */
+ public static final String FEATURE_FELICA = "android.hardware.felica";
+
+ /** See {@code PackageManager#FEATURE_RAM_LOW}. */
+ public static final String FEATURE_RAM_LOW = "android.hardware.ram.low";
+
+ /** See {@code PackageManager#FEATURE_RAM_NORMAL}. */
+ public static final String FEATURE_RAM_NORMAL = "android.hardware.ram.normal";
+
+ /** See {@code PackageManager#FEATURE_MICROPHONE}. */
+ public static final String FEATURE_MICROPHONE = "android.hardware.microphone";
+
+ /** See {@code PackageManager#FEATURE_NFC}. */
+ public static final String FEATURE_NFC = "android.hardware.nfc";
+
+ /** See {@code PackageManager#FEATURE_NFC_HCE}. */
+ public static final String FEATURE_NFC_HCE = "android.hardware.nfc.hce";
+
+ /** See {@code PackageManager#FEATURE_NFC_HOST_CARD_EMULATION}. */
+ public static final String FEATURE_NFC_HOST_CARD_EMULATION = "android.hardware.nfc.hce";
+
+ /** See {@code PackageManager#FEATURE_NFC_HOST_CARD_EMULATION_NFCF}. */
+ public static final String FEATURE_NFC_HOST_CARD_EMULATION_NFCF = "android.hardware.nfc.hcef";
+
+ /** See {@code PackageManager#FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC}. */
+ public static final String FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC =
+ "android.hardware.nfc.uicc";
+
+ /** See {@code PackageManager#FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE}. */
+ public static final String FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE = "android.hardware.nfc.ese";
+
+ /** See {@code PackageManager#FEATURE_NFC_BEAM}. */
+ public static final String FEATURE_NFC_BEAM = "android.sofware.nfc.beam";
+
+ /** See {@code PackageManager#FEATURE_NFC_ANY}. */
+ public static final String FEATURE_NFC_ANY = "android.hardware.nfc.any";
+
+ /** See {@code PackageManager#FEATURE_SE_OMAPI_UICC}. */
+ public static final String FEATURE_SE_OMAPI_UICC = "android.hardware.se.omapi.uicc";
+
+ /** See {@code PackageManager#FEATURE_SE_OMAPI_ESE}. */
+ public static final String FEATURE_SE_OMAPI_ESE = "android.hardware.se.omapi.ese";
+
+ /** See {@code PackageManager#FEATURE_SE_OMAPI_SD}. */
+ public static final String FEATURE_SE_OMAPI_SD = "android.hardware.se.omapi.sd";
+
+ /** See {@code PackageManager#FEATURE_SECURITY_MODEL_COMPATIBLE}. */
+ public static final String FEATURE_SECURITY_MODEL_COMPATIBLE =
+ "android.hardware.security.model.compatible";
+
+ /** See {@code PackageManager#FEATURE_OPENGLES_EXTENSION_PACK}. */
+ public static final String FEATURE_OPENGLES_EXTENSION_PACK = "android.hardware.opengles.aep";
+
+ /** See {@code PackageManager#FEATURE_VULKAN_HARDWARE_LEVEL}. */
+ public static final String FEATURE_VULKAN_HARDWARE_LEVEL = "android.hardware.vulkan.level";
+
+ /** See {@code PackageManager#FEATURE_VULKAN_HARDWARE_COMPUTE}. */
+ public static final String FEATURE_VULKAN_HARDWARE_COMPUTE = "android.hardware.vulkan.compute";
+
+ /** See {@code PackageManager#FEATURE_VULKAN_HARDWARE_VERSION}. */
+ public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version";
+
+ /** See {@code PackageManager#FEATURE_VULKAN_DEQP_LEVEL}. */
+ public static final String FEATURE_VULKAN_DEQP_LEVEL = "android.software.vulkan.deqp.level";
+
+ /** See {@code PackageManager#FEATURE_OPENGLES_DEQP_LEVEL}. */
+ public static final String FEATURE_OPENGLES_DEQP_LEVEL = "android.software.opengles.deqp.level";
+
+ /** See {@code PackageManager#FEATURE_BROADCAST_RADIO}. */
+ public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio";
+
+ /** See {@code PackageManager#FEATURE_SECURE_LOCK_SCREEN}. */
+ public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen";
+
+ /** See {@code PackageManager#FEATURE_SENSOR_ACCELEROMETER}. */
+ public static final String FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer";
+
+ /** See {@code PackageManager#FEATURE_SENSOR_BAROMETER}. */
+ public static final String FEATURE_SENSOR_BAROMETER = "android.hardware.sensor.barometer";
+
+ /** See {@code PackageManager#FEATURE_SENSOR_COMPASS}. */
+ public static final String FEATURE_SENSOR_COMPASS = "android.hardware.sensor.compass";
+
+ /** See {@code PackageManager#FEATURE_SENSOR_GYROSCOPE}. */
+ public static final String FEATURE_SENSOR_GYROSCOPE = "android.hardware.sensor.gyroscope";
+
+ /** See {@code PackageManager#FEATURE_SENSOR_LIGHT}. */
+ public static final String FEATURE_SENSOR_LIGHT = "android.hardware.sensor.light";
+
+ /** See {@code PackageManager#FEATURE_SENSOR_PROXIMITY}. */
+ public static final String FEATURE_SENSOR_PROXIMITY = "android.hardware.sensor.proximity";
+
+ /** See {@code PackageManager#FEATURE_SENSOR_STEP_COUNTER}. */
+ public static final String FEATURE_SENSOR_STEP_COUNTER = "android.hardware.sensor.stepcounter";
+
+ /** See {@code PackageManager#FEATURE_SENSOR_STEP_DETECTOR}. */
+ public static final String FEATURE_SENSOR_STEP_DETECTOR = "android.hardware.sensor.stepdetector";
+
+ /** See {@code PackageManager#FEATURE_SENSOR_HEART_RATE}. */
+ public static final String FEATURE_SENSOR_HEART_RATE = "android.hardware.sensor.heartrate";
+
+ /** See {@code PackageManager#FEATURE_SENSOR_HEART_RATE_ECG}. */
+ public static final String FEATURE_SENSOR_HEART_RATE_ECG =
+ "android.hardware.sensor.heartrate.ecg";
+
+ /** See {@code PackageManager#FEATURE_SENSOR_RELATIVE_HUMIDITY}. */
+ public static final String FEATURE_SENSOR_RELATIVE_HUMIDITY =
+ "android.hardware.sensor.relative_humidity";
+
+ /** See {@code PackageManager#FEATURE_SENSOR_AMBIENT_TEMPERATURE}. */
+ public static final String FEATURE_SENSOR_AMBIENT_TEMPERATURE =
+ "android.hardware.sensor.ambient_temperature";
+
+ /** See {@code PackageManager#FEATURE_SENSOR_HINGE_ANGLE}. */
+ public static final String FEATURE_SENSOR_HINGE_ANGLE = "android.hardware.sensor.hinge_angle";
+
+ /** See {@code PackageManager#FEATURE_HIFI_SENSORS}. */
+ public static final String FEATURE_HIFI_SENSORS =
+ "android.hardware.sensor.hifi_sensors";
+
+ /** See {@code PackageManager#FEATURE_ASSIST_GESTURE}. */
+ public static final String FEATURE_ASSIST_GESTURE = "android.hardware.sensor.assist";
+
+ /** See {@code PackageManager#FEATURE_TELEPHONY}. */
+ public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+
+ /** See {@code PackageManager#FEATURE_TELEPHONY_CDMA}. */
+ public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
+
+ /** See {@code PackageManager#FEATURE_TELEPHONY_GSM}. */
+ public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
+
+ /** See {@code PackageManager#FEATURE_TELEPHONY_CARRIERLOCK}. */
+ public static final String FEATURE_TELEPHONY_CARRIERLOCK =
+ "android.hardware.telephony.carrierlock";
+
+ /** See {@code PackageManager#FEATURE_TELEPHONY_EUICC}. */
+ public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
+
+ /** See {@code PackageManager#FEATURE_TELEPHONY_MBMS}. */
+ public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
+
+ /** See {@code PackageManager#FEATURE_TELEPHONY_IMS}. */
+ public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
+
+ /** See {@code PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}. */
+ public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION =
+ "android.hardware.telephony.ims.singlereg";
+
+ /** See {@code PackageManager#FEATURE_UWB}. */
+ public static final String FEATURE_UWB = "android.hardware.uwb";
+
+ /** See {@code PackageManager#FEATURE_USB_HOST}. */
+ public static final String FEATURE_USB_HOST = "android.hardware.usb.host";
+
+ /** See {@code PackageManager#FEATURE_USB_ACCESSORY}. */
+ public static final String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
+
+ /** See {@code PackageManager#FEATURE_SIP}. */
+ public static final String FEATURE_SIP = "android.software.sip";
+
+ /** See {@code PackageManager#FEATURE_SIP_VOIP}. */
+ public static final String FEATURE_SIP_VOIP = "android.software.sip.voip";
+
+ /** See {@code PackageManager#FEATURE_CONNECTION_SERVICE}. */
+ public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
+
+ /** See {@code PackageManager#FEATURE_TOUCHSCREEN}. */
+ public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
+
+ /** See {@code PackageManager#FEATURE_TOUCHSCREEN_MULTITOUCH}. */
+ public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
+
+ /** See {@code PackageManager#FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT}. */
+ public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT = "android.hardware.touchscreen.multitouch.distinct";
+
+ /** See {@code PackageManager#FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND}. */
+ public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND = "android.hardware.touchscreen.multitouch.jazzhand";
+
+ /** See {@code PackageManager#FEATURE_FAKETOUCH}. */
+ public static final String FEATURE_FAKETOUCH = "android.hardware.faketouch";
+
+ /** See {@code PackageManager#FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT}. */
+ public static final String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
+
+ /** See {@code PackageManager#FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND}. */
+ public static final String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand";
+
+ /** See {@code PackageManager#FEATURE_FINGERPRINT}. */
+ public static final String FEATURE_FINGERPRINT = "android.hardware.fingerprint";
+
+ /** See {@code PackageManager#FEATURE_FACE}. */
+ public static final String FEATURE_FACE = "android.hardware.biometrics.face";
+
+ /** See {@code PackageManager#FEATURE_IRIS}. */
+ public static final String FEATURE_IRIS = "android.hardware.biometrics.iris";
+
+ /** See {@code PackageManager#FEATURE_SCREEN_PORTRAIT}. */
+ public static final String FEATURE_SCREEN_PORTRAIT = "android.hardware.screen.portrait";
+
+ /** See {@code PackageManager#FEATURE_SCREEN_LANDSCAPE}. */
+ public static final String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape";
+
+ /** See {@code PackageManager#FEATURE_LIVE_WALLPAPER}. */
+ public static final String FEATURE_LIVE_WALLPAPER = "android.software.live_wallpaper";
+
+ /** See {@code PackageManager#FEATURE_APP_WIDGETS}. */
+ public static final String FEATURE_APP_WIDGETS = "android.software.app_widgets";
+
+ /** See {@code PackageManager#FEATURE_CANT_SAVE_STATE}. */
+ public static final String FEATURE_CANT_SAVE_STATE = "android.software.cant_save_state";
+
+ /** See {@code PackageManager#FEATURE_GAME_SERVICE}. */
+ public static final String FEATURE_GAME_SERVICE = "android.software.game_service";
+
+ /** See {@code PackageManager#FEATURE_VOICE_RECOGNIZERS}. */
+ public static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers";
+
+ /** See {@code PackageManager#FEATURE_HOME_SCREEN}. */
+ public static final String FEATURE_HOME_SCREEN = "android.software.home_screen";
+
+ /** See {@code PackageManager#FEATURE_INPUT_METHODS}. */
+ public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
+
+ /** See {@code PackageManager#FEATURE_DEVICE_ADMIN}. */
+ public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
+
+ /** See {@code PackageManager#FEATURE_LEANBACK}. */
+ public static final String FEATURE_LEANBACK = "android.software.leanback";
+
+ /** See {@code PackageManager#FEATURE_LEANBACK_ONLY}. */
+ public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
+
+ /** See {@code PackageManager#FEATURE_LIVE_TV}. */
+ public static final String FEATURE_LIVE_TV = "android.software.live_tv";
+
+ /** See {@code PackageManager#FEATURE_WIFI}. */
+ public static final String FEATURE_WIFI = "android.hardware.wifi";
+
+ /** See {@code PackageManager#FEATURE_WIFI_DIRECT}. */
+ public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
+
+ /** See {@code PackageManager#FEATURE_WIFI_AWARE}. */
+ public static final String FEATURE_WIFI_AWARE = "android.hardware.wifi.aware";
+
+ /** See {@code PackageManager#FEATURE_WIFI_PASSPOINT}. */
+ public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
+
+ /** See {@code PackageManager#FEATURE_WIFI_RTT}. */
+ public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt";
+
+ /** See {@code PackageManager#FEATURE_LOWPAN}. */
+ public static final String FEATURE_LOWPAN = "android.hardware.lowpan";
+
+ /** See {@code PackageManager#FEATURE_AUTOMOTIVE}. */
+ public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
+
+ /** See {@code PackageManager#FEATURE_TELEVISION}. */
+ public static final String FEATURE_TELEVISION = "android.hardware.type.television";
+
+ /** See {@code PackageManager#FEATURE_WATCH}. */
+ public static final String FEATURE_WATCH = "android.hardware.type.watch";
+
+ /** See {@code PackageManager#FEATURE_EMBEDDED}. */
+ public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded";
+
+ /** See {@code PackageManager#FEATURE_PC}. */
+ public static final String FEATURE_PC = "android.hardware.type.pc";
+
+ /** See {@code PackageManager#FEATURE_PRINTING}. */
+ public static final String FEATURE_PRINTING = "android.software.print";
+
+ /** See {@code PackageManager#FEATURE_COMPANION_DEVICE_SETUP}. */
+ public static final String FEATURE_COMPANION_DEVICE_SETUP =
+ "android.software.companion_device_setup";
+
+ /** See {@code PackageManager#FEATURE_BACKUP}. */
+ public static final String FEATURE_BACKUP = "android.software.backup";
+
+ /** See {@code PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT}. */
+ public static final String FEATURE_FREEFORM_WINDOW_MANAGEMENT =
+ "android.software.freeform_window_management";
+
+ /** See {@code PackageManager#FEATURE_PICTURE_IN_PICTURE}. */
+ public static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
+
+ /** See {@code PackageManager#FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS}. */
+ public static final String FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS =
+ "android.software.activities_on_secondary_displays";
+
+ /** See {@code PackageManager#FEATURE_MANAGED_USERS}. */
+ public static final String FEATURE_MANAGED_USERS = "android.software.managed_users";
+
+ /** See {@code PackageManager#FEATURE_MANAGED_PROFILES}. */
+ public static final String FEATURE_MANAGED_PROFILES = "android.software.managed_users";
+
+ /** See {@code PackageManager#FEATURE_NEARBY}. */
+ public static final String FEATURE_NEARBY = "android.software.nearby";
+
+ /** See {@code PackageManager#FEATURE_VERIFIED_BOOT}. */
+ public static final String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
+
+ /** See {@code PackageManager#FEATURE_SECURELY_REMOVES_USERS}. */
+ public static final String FEATURE_SECURELY_REMOVES_USERS =
+ "android.software.securely_removes_users";
+
+ /** See {@code PackageManager#FEATURE_FILE_BASED_ENCRYPTION}. */
+ public static final String FEATURE_FILE_BASED_ENCRYPTION =
+ "android.software.file_based_encryption";
+
+ /** See {@code PackageManager#FEATURE_ADOPTABLE_STORAGE}. */
+ public static final String FEATURE_ADOPTABLE_STORAGE =
+ "android.software.adoptable_storage";
+
+ /** See {@code PackageManager#FEATURE_WEBVIEW}. */
+ public static final String FEATURE_WEBVIEW = "android.software.webview";
+
+ /** See {@code PackageManager#FEATURE_ETHERNET}. */
+ public static final String FEATURE_ETHERNET = "android.hardware.ethernet";
+
+ /** See {@code PackageManager#FEATURE_HDMI_CEC}. */
+ public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
+
+ /** See {@code PackageManager#FEATURE_GAMEPAD}. */
+ public static final String FEATURE_GAMEPAD = "android.hardware.gamepad";
+
+ /** See {@code PackageManager#FEATURE_MIDI}. */
+ public static final String FEATURE_MIDI = "android.software.midi";
+
+ /** See {@code PackageManager#FEATURE_VR_MODE}. */
+ public static final String FEATURE_VR_MODE = "android.software.vr.mode";
+
+ /** See {@code PackageManager#FEATURE_VR_MODE_HIGH_PERFORMANCE}. */
+ public static final String FEATURE_VR_MODE_HIGH_PERFORMANCE
+ = "android.hardware.vr.high_performance";
+
+ /** See {@code PackageManager#FEATURE_AUTOFILL}. */
+ public static final String FEATURE_AUTOFILL = "android.software.autofill";
+
+ /** See {@code PackageManager#FEATURE_VR_HEADTRACKING}. */
+ public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking";
+
+ /** See {@code PackageManager#FEATURE_HARDWARE_KEYSTORE}. */
+ public static final String FEATURE_HARDWARE_KEYSTORE = "android.hardware.hardware_keystore";
+
+ /** See {@code PackageManager#FEATURE_STRONGBOX_KEYSTORE}. */
+ public static final String FEATURE_STRONGBOX_KEYSTORE =
+ "android.hardware.strongbox_keystore";
+
+ /** See {@code PackageManager#FEATURE_SLICES_DISABLED}. */
+ public static final String FEATURE_SLICES_DISABLED = "android.software.slices_disabled";
+
+ /** See {@code PackageManager#FEATURE_DEVICE_UNIQUE_ATTESTATION}. */
+ public static final String FEATURE_DEVICE_UNIQUE_ATTESTATION =
+ "android.hardware.device_unique_attestation";
+
+ /** See {@code PackageManager#FEATURE_DEVICE_ID_ATTESTATION}. */
+ public static final String FEATURE_DEVICE_ID_ATTESTATION =
+ "android.software.device_id_attestation";
+
+ /** See {@code PackageManager#FEATURE_IPSEC_TUNNELS}. */
+ public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
+
+ /** See {@code PackageManager#FEATURE_CONTROLS}. */
+ public static final String FEATURE_CONTROLS = "android.software.controls";
+
+ /** See {@code PackageManager#FEATURE_REBOOT_ESCROW}. */
+ public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
+
+ /** See {@code PackageManager#FEATURE_INCREMENTAL_DELIVERY}. */
+ public static final String FEATURE_INCREMENTAL_DELIVERY =
+ "android.software.incremental_delivery";
+
+ /** See {@code PackageManager#FEATURE_TUNER}. */
+ public static final String FEATURE_TUNER = "android.hardware.tv.tuner";
+
+ /** See {@code PackageManager#FEATURE_APP_ENUMERATION}. */
+ public static final String FEATURE_APP_ENUMERATION = "android.software.app_enumeration";
+
+ /** See {@code PackageManager#FEATURE_KEYSTORE_SINGLE_USE_KEY}. */
+ public static final String FEATURE_KEYSTORE_SINGLE_USE_KEY =
+ "android.hardware.keystore.single_use_key";
+
+ /** See {@code PackageManager#FEATURE_KEYSTORE_LIMITED_USE_KEY}. */
+ public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY =
+ "android.hardware.keystore.limited_use_key";
+
+ /** See {@code PackageManager#FEATURE_KEYSTORE_APP_ATTEST_KEY}. */
+ public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY =
+ "android.hardware.keystore.app_attest_key";
+
+ /** See {@code PackageManager#FEATURE_APP_COMPAT_OVERRIDES}. */
+ public static final String FEATURE_APP_COMPAT_OVERRIDES =
+ "android.software.app_compat_overrides";
+
+ /** See {@code PackageManager#FEATURE_COMMUNAL_MODE}. */
+ public static final String FEATURE_COMMUNAL_MODE = "android.software.communal_mode";
+}
diff --git a/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/permissions/CommonPermissions.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/permissions/CommonPermissions.java
new file mode 100644
index 0000000..69d191c
--- /dev/null
+++ b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/permissions/CommonPermissions.java
@@ -0,0 +1,1845 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.nene.permissions;
+
+/** Permissions helper methods common to host and device. */
+public class CommonPermissions {
+
+ CommonPermissions() {
+
+ }
+
+ /** See {@code Manifest#READ_CONTACTS} */
+ public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";
+
+ /** See {@code Manifest#WRITE_CONTACTS} */
+ public static final String WRITE_CONTACTS = "android.permission.WRITE_CONTACTS";
+
+ /** See {@code Manifest#SET_DEFAULT_ACCOUNT_FOR_CONTACTS} */
+ public static final String SET_DEFAULT_ACCOUNT_FOR_CONTACTS =
+ "android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS";
+
+ /** See {@code Manifest#READ_CALENDAR} */
+ public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
+
+ /** See {@code Manifest#WRITE_CALENDAR} */
+ public static final String WRITE_CALENDAR = "android.permission.WRITE_CALENDAR";
+
+ /** See {@code Manifest#ACCESS_MESSAGES_ON_ICC} */
+ public static final String ACCESS_MESSAGES_ON_ICC = "android.permission"
+ + ".ACCESS_MESSAGES_ON_ICC";
+
+ /** See {@code Manifest#SEND_SMS} */
+ public static final String SEND_SMS = "android.permission.SEND_SMS";
+
+ /** See {@code Manifest#RECEIVE_SMS} */
+ public static final String RECEIVE_SMS = "android.permission.RECEIVE_SMS";
+
+ /** See {@code Manifest#READ_SMS} */
+ public static final String READ_SMS = "android.permission.READ_SMS";
+
+ /** See {@code Manifest#RECEIVE_WAP_PUSH} */
+ public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
+
+ /** See {@code Manifest#RECEIVE_MMS} */
+ public static final String RECEIVE_MMS = "android.permission.RECEIVE_MMS";
+
+ /** See {@code Manifest#BIND_CELL_BROADCAST_SERVICE} */
+ public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission"
+ + ".BIND_CELL_BROADCAST_SERVICE";
+
+ /** See {@code Manifest#READ_CELL_BROADCASTS} */
+ public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
+
+ /** See {@code Manifest#WRITE_EXTERNAL_STORAGE} */
+ public static final String WRITE_EXTERNAL_STORAGE = "android.permission.WRITE_EXTERNAL_STORAGE";
+
+ /** See {@code Manifest#ACCESS_MEDIA_LOCATION} */
+ public static final String ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION";
+
+ /** See {@code Manifest#WRITE_OBB} */
+ public static final String WRITE_OBB = "android.permission.WRITE_OBB";
+
+ /** See {@code Manifest#MANAGE_EXTERNAL_STORAGE} */
+ public static final String MANAGE_EXTERNAL_STORAGE = "android.permission"
+ + ".MANAGE_EXTERNAL_STORAGE";
+
+ /** See {@code Manifest#MANAGE_MEDIA} */
+ public static final String MANAGE_MEDIA = "android.permission.MANAGE_MEDIA";
+
+ /** See {@code Manifest#ACCESS_FINE_LOCATION} */
+ public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
+
+ /** See {@code Manifest#ACCESS_COARSE_LOCATION} */
+ public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
+
+ /** See {@code Manifest#ACCESS_BACKGROUND_LOCATION} */
+ public static final String ACCESS_BACKGROUND_LOCATION =
+ "android.permission.ACCESS_BACKGROUND_LOCATION";
+
+ /** See {@code Manifest#ACCESS_IMS_CALL_SERVICE} */
+ public static final String ACCESS_IMS_CALL_SERVICE = "android.permission"
+ + ".ACCESS_IMS_CALL_SERVICE";
+
+ /** See {@code Manifest#PERFORM_IMS_SINGLE_REGISTRATION} */
+ public static final String PERFORM_IMS_SINGLE_REGISTRATION = "android.permission"
+ + ".PERFORM_IMS_SINGLE_REGISTRATION";
+
+ /** See {@code Manifest#READ_CALL_LOG} */
+ public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
+
+ /** See {@code Manifest#PROCESS_OUTGOING_CALLS} */
+ public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
+
+ /** See {@code Manifest#READ_PHONE_STATE} */
+ public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+
+ /** See {@code Manifest#READ_BASIC_PHONE_STATE} */
+ public static final String READ_BASIC_PHONE_STATE = "android.permission.READ_BASIC_PHONE_STATE";
+
+ /** See {@code Manifest#READ_PHONE_NUMBERS} */
+ public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
+
+ /** See {@code Manifest#CALL_PHONE} */
+ public static final String CALL_PHONE = "android.permission.CALL_PHONE";
+
+ /** See {@code Manifest#ADD_VOICEMAIL} */
+ public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL";
+
+ /** See {@code Manifest#USE_SIP} */
+ public static final String USE_SIP = "android.permission.USE_SIP";
+
+ /** See {@code Manifest#ANSWER_PHONE_CALLS} */
+ public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS";
+
+ /** See {@code Manifest#MANAGE_OWN_CALLS} */
+ public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
+
+ /** See {@code Manifest#CALL_COMPANION_APP} */
+ public static final String CALL_COMPANION_APP = "android.permission.CALL_COMPANION_APP";
+
+ /** See {@code Manifest#EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS} */
+ public static final String EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS = "android.permission"
+ + ".EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS";
+
+ /** See {@code Manifest#ACCEPT_HANDOVER} */
+ public static final String ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER";
+
+ /** See {@code Manifest#RECORD_AUDIO} */
+ public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
+
+ /** See {@code Manifest#RECORD_BACKGROUND_AUDIO} */
+ public static final String RECORD_BACKGROUND_AUDIO =
+ "android.permission.RECORD_BACKGROUND_AUDIO";
+
+ /** See {@code Manifest#ACTIVITY_RECOGNITION} */
+ public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
+
+ /** See {@code Manifest#ACCESS_UCE_PRESENCE_SERVICE} */
+ public static final String ACCESS_UCE_PRESENCE_SERVICE =
+ "android.permission.ACCESS_UCE_PRESENCE_SERVICE";
+
+ /** See {@code Manifest#ACCESS_UCE_OPTIONS_SERVICE} */
+ public static final String ACCESS_UCE_OPTIONS_SERVICE =
+ "android.permission.ACCESS_UCE_OPTIONS_SERVICE";
+
+ /** See {@code Manifest#CAMERA} */
+ public static final String CAMERA = "android.permission.CAMERA";
+
+ /** See {@code Manifest#BACKGROUND_CAMERA} */
+ public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
+
+ /** See {@code Manifest#SYSTEM_CAMERA} */
+ public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
+
+ /** See {@code Manifest#CAMERA_OPEN_CLOSE_LISTENER} */
+ public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission"
+ + ".CAMERA_OPEN_CLOSE_LISTENER";
+
+ /** See {@code Manifest#HIGH_SAMPLING_RATE_SENSORS} */
+ public static final String HIGH_SAMPLING_RATE_SENSORS =
+ "android.permission.HIGH_SAMPLING_RATE_SENSORS";
+
+ /** See {@code Manifest#BODY_SENSORS} */
+ public static final String BODY_SENSORS = "android.permission.BODY_SENSORS";
+
+ /** See {@code Manifest#USE_FINGERPRINT} */
+ public static final String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT";
+
+ /** See {@code Manifest#USE_BIOMETRIC} */
+ public static final String USE_BIOMETRIC = "android.permission.USE_BIOMETRIC";
+
+ /** See {@code Manifest#POST_NOTIFICATIONS} */
+ public static final String POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS";
+
+ /** See {@code Manifest#READ_PROFILE} */
+ public static final String READ_PROFILE = "android.permission.READ_PROFILE";
+
+ /** See {@code Manifest#WRITE_PROFILE} */
+ public static final String WRITE_PROFILE = "android.permission.WRITE_PROFILE";
+
+ /** See {@code Manifest#READ_SOCIAL_STREAM} */
+ public static final String READ_SOCIAL_STREAM = "android.permission.READ_SOCIAL_STREAM";
+
+ /** See {@code Manifest#WRITE_SOCIAL_STREAM} */
+ public static final String WRITE_SOCIAL_STREAM = "android.permission.WRITE_SOCIAL_STREAM";
+
+ /** See {@code Manifest#READ_USER_DICTIONARY} */
+ public static final String READ_USER_DICTIONARY = "android.permission.READ_USER_DICTIONARY";
+
+ /** See {@code Manifest#WRITE_USER_DICTIONARY} */
+ public static final String WRITE_USER_DICTIONARY = "android.permission.WRITE_USER_DICTIONARY";
+
+ /** See {@code Manifest#WRITE_SMS} */
+ public static final String WRITE_SMS = "android.permission.WRITE_SMS";
+
+ /** See {@code Manifest#READ_HISTORY_BOOKMARKS} */
+ public static final String READ_HISTORY_BOOKMARKS =
+ "com.android.browser.permission.READ_HISTORY_BOOKMARKS";
+
+ /** See {@code Manifest#WRITE_HISTORY_BOOKMARKS} */
+ public static final String WRITE_HISTORY_BOOKMARKS =
+ "com.android.browser.permission.WRITE_HISTORY_BOOKMARKS";
+
+ /** See {@code Manifest#AUTHENTICATE_ACCOUNTS} */
+ public static final String AUTHENTICATE_ACCOUNTS = "android.permission.AUTHENTICATE_ACCOUNTS";
+
+ /** See {@code Manifest#MANAGE_ACCOUNTS} */
+ public static final String MANAGE_ACCOUNTS = "android.permission.MANAGE_ACCOUNTS";
+
+ /** See {@code Manifest#USE_CREDENTIALS} */
+ public static final String USE_CREDENTIALS = "android.permission.USE_CREDENTIALS";
+
+ /** See {@code Manifest#SUBSCRIBED_FEEDS_READ} */
+ public static final String SUBSCRIBED_FEEDS_READ = "android.permission.SUBSCRIBED_FEEDS_READ";
+
+ /** See {@code Manifest#SUBSCRIBED_FEEDS_WRITE} */
+ public static final String SUBSCRIBED_FEEDS_WRITE = "android.permission"
+ + ".SUBSCRIBED_FEEDS_WRITE";
+
+ /** See {@code Manifest#FLASHLIGHT} */
+ public static final String FLASHLIGHT = "android.permission.FLASHLIGHT";
+
+ /** See {@code Manifest#SEND_RESPOND_VIA_MESSAGE} */
+ public static final String SEND_RESPOND_VIA_MESSAGE =
+ "android.permission.SEND_RESPOND_VIA_MESSAGE";
+
+ /** See {@code Manifest#SEND_SMS_NO_CONFIRMATION} */
+ public static final String SEND_SMS_NO_CONFIRMATION = "android.permission"
+ + ".SEND_SMS_NO_CONFIRMATION";
+
+ /** See {@code Manifest#CARRIER_FILTER_SMS} */
+ public static final String CARRIER_FILTER_SMS = "android.permission.CARRIER_FILTER_SMS";
+
+ /** See {@code Manifest#RECEIVE_EMERGENCY_BROADCAST} */
+ public static final String RECEIVE_EMERGENCY_BROADCAST =
+ "android.permission.RECEIVE_EMERGENCY_BROADCAST";
+
+ /** See {@code Manifest#RECEIVE_BLUETOOTH_MAP} */
+ public static final String RECEIVE_BLUETOOTH_MAP = "android.permission.RECEIVE_BLUETOOTH_MAP";
+
+ /** See {@code Manifest#MODIFY_CELL_BROADCASTS} */
+ public static final String MODIFY_CELL_BROADCASTS =
+ "android.permission.MODIFY_CELL_BROADCASTS";
+
+ /** See {@code Manifest#SET_ALARM} */
+ public static final String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
+
+ /** See {@code Manifest#WRITE_VOICEMAIL} */
+ public static final String WRITE_VOICEMAIL = "com.android.voicemail.permission.WRITE_VOICEMAIL";
+
+ /** See {@code Manifest#READ_VOICEMAIL} */
+ public static final String READ_VOICEMAIL = "com.android.voicemail.permission.READ_VOICEMAIL";
+ /** See {@code Manifest#ACCESS_LOCATION_EXTRA_COMMANDS} */
+ public static final String ACCESS_LOCATION_EXTRA_COMMANDS = "android.permission"
+ + ".ACCESS_LOCATION_EXTRA_COMMANDS";
+ /** See {@code Manifest#INSTALL_LOCATION_PROVIDER} */
+ public static final String INSTALL_LOCATION_PROVIDER = "android.permission"
+ + ".INSTALL_LOCATION_PROVIDER";
+ /** See {@code Manifest#INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE} */
+ public static final String INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE = "android"
+ + ".permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE";
+ /** See {@code Manifest#BIND_TIME_ZONE_PROVIDER_SERVICE} */
+ public static final String BIND_TIME_ZONE_PROVIDER_SERVICE =
+ "android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE";
+ /** See {@code Manifest#HDMI_CEC} */
+ public static final String HDMI_CEC = "android.permission.HDMI_CEC";
+ /** See {@code Manifest#LOCATION_HARDWARE} */
+ public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
+ /** See {@code Manifest#ACCESS_CONTEXT_HUB} */
+ public static final String ACCESS_CONTEXT_HUB = "android.permission.ACCESS_CONTEXT_HUB";
+ /** See {@code Manifest#ACCESS_MOCK_LOCATION} */
+ public static final String ACCESS_MOCK_LOCATION = "android.permission.ACCESS_MOCK_LOCATION";
+ /** See {@code Manifest#AUTOMOTIVE_GNSS_CONTROLS} */
+ public static final String AUTOMOTIVE_GNSS_CONTROLS =
+ "android.permission.AUTOMOTIVE_GNSS_CONTROLS";
+ /** See {@code Manifest#INTERNET} */
+ public static final String INTERNET = "android.permission.INTERNET";
+ /** See {@code Manifest#ACCESS_NETWORK_STATE} */
+ public static final String ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE";
+ /** See {@code Manifest#ACCESS_WIFI_STATE} */
+ public static final String ACCESS_WIFI_STATE = "android.permission.ACCESS_WIFI_STATE";
+ /** See {@code Manifest#CHANGE_WIFI_STATE} */
+ public static final String CHANGE_WIFI_STATE = "android.permission.CHANGE_WIFI_STATE";
+ /** See {@code Manifest#MANAGE_WIFI_AUTO_JOIN} */
+ public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN";
+ /** See {@code Manifest#MANAGE_IPSEC_TUNNELS} */
+ public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
+ /** See {@code Manifest#MANAGE_TEST_NETWORKS} */
+ public static final String MANAGE_TEST_NETWORKS = "android.permission.MANAGE_TEST_NETWORKS";
+ /** See {@code Manifest#READ_WIFI_CREDENTIAL} */
+ public static final String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL";
+ /** See {@code Manifest#TETHER_PRIVILEGED} */
+ public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
+ /** See {@code Manifest#RECEIVE_WIFI_CREDENTIAL_CHANGE} */
+ public static final String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission"
+ + ".RECEIVE_WIFI_CREDENTIAL_CHANGE";
+ /** See {@code Manifest#OVERRIDE_WIFI_CONFIG} */
+ public static final String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG";
+ /** See {@code Manifest#SCORE_NETWORKS} */
+ public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
+ /** See {@code Manifest#REQUEST_NETWORK_SCORES} */
+ public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
+ /** See {@code Manifest#RESTART_WIFI_SUBSYSTEM} */
+ public static final String RESTART_WIFI_SUBSYSTEM = "android.permission"
+ + ".RESTART_WIFI_SUBSYSTEM";
+ /** See {@code Manifest#NETWORK_AIRPLANE_MODE} */
+ public static final String NETWORK_AIRPLANE_MODE = "android.permission.NETWORK_AIRPLANE_MODE";
+ /** See {@code Manifest#NETWORK_STACK} */
+ public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
+ /** See {@code Manifest#OBSERVE_NETWORK_POLICY} */
+ public static final String OBSERVE_NETWORK_POLICY = "android.permission"
+ + ".OBSERVE_NETWORK_POLICY";
+ /** See {@code Manifest#NETWORK_FACTORY} */
+ public static final String NETWORK_FACTORY = "android.permission.NETWORK_FACTORY";
+ /** See {@code Manifest#NETWORK_STATS_PROVIDER} */
+ public static final String NETWORK_STATS_PROVIDER = "android.permission.NETWORK_STATS_PROVIDER";
+ /** See {@code Manifest#NETWORK_SETTINGS} */
+ public static final String NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS";
+ /** See {@code Manifest#RADIO_SCAN_WITHOUT_LOCATION} */
+ public static final String RADIO_SCAN_WITHOUT_LOCATION =
+ "android.permission.RADIO_SCAN_WITHOUT_LOCATION";
+ /** See {@code Manifest#NETWORK_SETUP_WIZARD} */
+ public static final String NETWORK_SETUP_WIZARD = "android.permission.NETWORK_SETUP_WIZARD";
+ /** See {@code Manifest#NETWORK_MANAGED_PROVISIONING} */
+ public static final String NETWORK_MANAGED_PROVISIONING = "android.permission"
+ + ".NETWORK_MANAGED_PROVISIONING";
+ /** See {@code Manifest#NETWORK_CARRIER_PROVISIONING} */
+ public static final String NETWORK_CARRIER_PROVISIONING =
+ "android.permission.NETWORK_CARRIER_PROVISIONING";
+ /** See {@code Manifest#ACCESS_LOWPAN_STATE} */
+ public static final String ACCESS_LOWPAN_STATE = "android.permission.ACCESS_LOWPAN_STATE";
+ /** See {@code Manifest#CHANGE_LOWPAN_STATE} */
+ public static final String CHANGE_LOWPAN_STATE = "android.permission.CHANGE_LOWPAN_STATE";
+ /** See {@code Manifest#READ_LOWPAN_CREDENTIAL} */
+ public static final String READ_LOWPAN_CREDENTIAL = "android.permission.READ_LOWPAN_CREDENTIAL";
+ /** See {@code Manifest#MANAGE_LOWPAN_INTERFACES} */
+ public static final String MANAGE_LOWPAN_INTERFACES = "android.permission"
+ + ".MANAGE_LOWPAN_INTERFACES";
+ /** See {@code Manifest#NETWORK_BYPASS_PRIVATE_DNS} */
+ public static final String NETWORK_BYPASS_PRIVATE_DNS =
+ "android.permission.NETWORK_BYPASS_PRIVATE_DNS";
+ /** See {@code Manifest#WIFI_SET_DEVICE_MOBILITY_STATE} */
+ public static final String WIFI_SET_DEVICE_MOBILITY_STATE =
+ "android.permission.WIFI_SET_DEVICE_MOBILITY_STATE";
+ /** See {@code Manifest#WIFI_UPDATE_USABILITY_STATS_SCORE} */
+ public static final String WIFI_UPDATE_USABILITY_STATS_SCORE = "android.permission"
+ + ".WIFI_UPDATE_USABILITY_STATS_SCORE";
+ /** See {@code Manifest#WIFI_UPDATE_COEX_UNSAFE_CHANNELS} */
+ public static final String WIFI_UPDATE_COEX_UNSAFE_CHANNELS = "android.permission"
+ + ".WIFI_UPDATE_COEX_UNSAFE_CHANNELS";
+ /** See {@code Manifest#WIFI_ACCESS_COEX_UNSAFE_CHANNELS} */
+ public static final String WIFI_ACCESS_COEX_UNSAFE_CHANNELS = "android.permission"
+ + ".WIFI_ACCESS_COEX_UNSAFE_CHANNELS";
+ /** See {@code Manifest#MANAGE_WIFI_COUNTRY_CODE} */
+ public static final String MANAGE_WIFI_COUNTRY_CODE =
+ "android.permission.MANAGE_WIFI_COUNTRY_CODE";
+ /** See {@code Manifest#CONTROL_OEM_PAID_NETWORK_PREFERENCE} */
+ public static final String CONTROL_OEM_PAID_NETWORK_PREFERENCE =
+ "android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE";
+ /** See {@code Manifest#BLUETOOTH} */
+ public static final String BLUETOOTH = "android.permission.BLUETOOTH";
+ /** See {@code Manifest#BLUETOOTH_SCAN} */
+ public static final String BLUETOOTH_SCAN = "android.permission.BLUETOOTH_SCAN";
+ /** See {@code Manifest#BLUETOOTH_CONNECT} */
+ public static final String BLUETOOTH_CONNECT = "android.permission.BLUETOOTH_CONNECT";
+ /** See {@code Manifest#BLUETOOTH_ADVERTISE} */
+ public static final String BLUETOOTH_ADVERTISE = "android.permission.BLUETOOTH_ADVERTISE";
+ /** See {@code Manifest#UWB_RANGING} */
+ public static final String UWB_RANGING = "android.permission.UWB_RANGING";
+ /** See {@code Manifest#NEARBY_WIFI_DEVICES} */
+ public static final String NEARBY_WIFI_DEVICES = "android.permission.NEARBY_WIFI_DEVICES";
+ /** See {@code Manifest#SUSPEND_APPS} */
+ public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
+ /** See {@code Manifest#BLUETOOTH_ADMIN} */
+ public static final String BLUETOOTH_ADMIN = "android.permission.BLUETOOTH_ADMIN";
+ /** See {@code Manifest#BLUETOOTH_PRIVILEGED} */
+ public static final String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED";
+ /** See {@code Manifest#BLUETOOTH_MAP} */
+ public static final String BLUETOOTH_MAP = "android.permission.BLUETOOTH_MAP";
+ /** See {@code Manifest#BLUETOOTH_STACK} */
+ public static final String BLUETOOTH_STACK = "android.permission.BLUETOOTH_STACK";
+ /** See {@code Manifest#VIRTUAL_INPUT_DEVICE} */
+ public static final String VIRTUAL_INPUT_DEVICE = "android.permission.VIRTUAL_INPUT_DEVICE";
+ /** See {@code Manifest#NFC} */
+ public static final String NFC = "android.permission.NFC";
+ /** See {@code Manifest#NFC_TRANSACTION_EVENT} */
+ public static final String NFC_TRANSACTION_EVENT = "android.permission.NFC_TRANSACTION_EVENT";
+ /** See {@code Manifest#NFC_PREFERRED_PAYMENT_INFO} */
+ public static final String NFC_PREFERRED_PAYMENT_INFO =
+ "android.permission.NFC_PREFERRED_PAYMENT_INFO";
+ /** See {@code Manifest#NFC_SET_CONTROLLER_ALWAYS_ON} */
+ public static final String NFC_SET_CONTROLLER_ALWAYS_ON = "android.permission"
+ + ".NFC_SET_CONTROLLER_ALWAYS_ON";
+ /** See {@code Manifest#SECURE_ELEMENT_PRIVILEGED_OPERATION} */
+ public static final String SECURE_ELEMENT_PRIVILEGED_OPERATION = "android.permission"
+ + ".SECURE_ELEMENT_PRIVILEGED_OPERATION";
+ /** See {@code Manifest#CONNECTIVITY_INTERNAL} */
+ public static final String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL";
+ /** See {@code Manifest#CONNECTIVITY_USE_RESTRICTED_NETWORKS} */
+ public static final String CONNECTIVITY_USE_RESTRICTED_NETWORKS =
+ "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS";
+ /** See {@code Manifest#NETWORK_SIGNAL_STRENGTH_WAKEUP} */
+ public static final String NETWORK_SIGNAL_STRENGTH_WAKEUP =
+ "android.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP";
+ /** See {@code Manifest#PACKET_KEEPALIVE_OFFLOAD} */
+ public static final String PACKET_KEEPALIVE_OFFLOAD =
+ "android.permission.PACKET_KEEPALIVE_OFFLOAD";
+ /** See {@code Manifest#RECEIVE_DATA_ACTIVITY_CHANGE} */
+ public static final String RECEIVE_DATA_ACTIVITY_CHANGE =
+ "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE";
+ /** See {@code Manifest#LOOP_RADIO} */
+ public static final String LOOP_RADIO = "android.permission.LOOP_RADIO";
+ /** See {@code Manifest#NFC_HANDOVER_STATUS} */
+ public static final String NFC_HANDOVER_STATUS = "android.permission.NFC_HANDOVER_STATUS";
+ /** See {@code Manifest#MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED} */
+ public static final String MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED =
+ "android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED";
+ /** See {@code Manifest#ENABLE_TEST_HARNESS_MODE} */
+ public static final String ENABLE_TEST_HARNESS_MODE =
+ "android.permission.ENABLE_TEST_HARNESS_MODE";
+ /** See {@code Manifest#GET_ACCOUNTS} */
+ public static final String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
+ /** See {@code Manifest#ACCOUNT_MANAGER} */
+ public static final String ACCOUNT_MANAGER = "android.permission.ACCOUNT_MANAGER";
+ /** See {@code Manifest#CHANGE_WIFI_MULTICAST_STATE} */
+ public static final String CHANGE_WIFI_MULTICAST_STATE = "android.permission"
+ + ".CHANGE_WIFI_MULTICAST_STATE";
+ /** See {@code Manifest#VIBRATE} */
+ public static final String VIBRATE = "android.permission.VIBRATE";
+ /** See {@code Manifest#VIBRATE_ALWAYS_ON} */
+ public static final String VIBRATE_ALWAYS_ON = "android.permission.VIBRATE_ALWAYS_ON";
+ /** See {@code Manifest#ACCESS_VIBRATOR_STATE} */
+ public static final String ACCESS_VIBRATOR_STATE = "android.permission.ACCESS_VIBRATOR_STATE";
+ /** See {@code Manifest#WAKE_LOCK} */
+ public static final String WAKE_LOCK = "android.permission.WAKE_LOCK";
+ /** See {@code Manifest#TRANSMIT_IR} */
+ public static final String TRANSMIT_IR = "android.permission.TRANSMIT_IR";
+ /** See {@code Manifest#MODIFY_AUDIO_SETTINGS} */
+ public static final String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS";
+ /** See {@code Manifest#MANAGE_FACTORY_RESET_PROTECTION} */
+ public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission"
+ + ".MANAGE_FACTORY_RESET_PROTECTION";
+ /** See {@code Manifest#MANAGE_USB} */
+ public static final String MANAGE_USB = "android.permission.MANAGE_USB";
+ /** See {@code Manifest#MANAGE_DEBUGGING} */
+ public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
+ /** See {@code Manifest#ACCESS_MTP} */
+ public static final String ACCESS_MTP = "android.permission.ACCESS_MTP";
+ /** See {@code Manifest#HARDWARE_TEST} */
+ public static final String HARDWARE_TEST = "android.permission.HARDWARE_TEST";
+ /** See {@code Manifest#MANAGE_DYNAMIC_SYSTEM} */
+ public static final String MANAGE_DYNAMIC_SYSTEM = "android.permission.MANAGE_DYNAMIC_SYSTEM";
+ /** See {@code Manifest#INSTALL_DYNAMIC_SYSTEM} */
+ public static final String INSTALL_DYNAMIC_SYSTEM = "android.permission"
+ + ".INSTALL_DYNAMIC_SYSTEM";
+ /** See {@code Manifest#ACCESS_BROADCAST_RADIO} */
+ public static final String ACCESS_BROADCAST_RADIO = "android.permission"
+ + ".ACCESS_BROADCAST_RADIO";
+ /** See {@code Manifest#ACCESS_FM_RADIO} */
+ public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
+ /** See {@code Manifest#NET_ADMIN} */
+ public static final String NET_ADMIN = "android.permission.NET_ADMIN";
+ /** See {@code Manifest#REMOTE_AUDIO_PLAYBACK} */
+ public static final String REMOTE_AUDIO_PLAYBACK = "android.permission.REMOTE_AUDIO_PLAYBACK";
+ /** See {@code Manifest#TV_INPUT_HARDWARE} */
+ public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE";
+ /** See {@code Manifest#CAPTURE_TV_INPUT} */
+ public static final String CAPTURE_TV_INPUT = "android.permission.CAPTURE_TV_INPUT";
+ /** See {@code Manifest#DVB_DEVICE} */
+ public static final String DVB_DEVICE = "android.permission.DVB_DEVICE";
+ /** See {@code Manifest#MANAGE_CARRIER_OEM_UNLOCK_STATE} */
+ public static final String MANAGE_CARRIER_OEM_UNLOCK_STATE = "android.permission"
+ + ".MANAGE_CARRIER_OEM_UNLOCK_STATE";
+ /** See {@code Manifest#MANAGE_USER_OEM_UNLOCK_STATE} */
+ public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission"
+ + ".MANAGE_USER_OEM_UNLOCK_STATE";
+ /** See {@code Manifest#READ_OEM_UNLOCK_STATE} */
+ public static final String READ_OEM_UNLOCK_STATE = "android.permission.READ_OEM_UNLOCK_STATE";
+ /** See {@code Manifest#OEM_UNLOCK_STATE} */
+ public static final String OEM_UNLOCK_STATE = "android.permission.OEM_UNLOCK_STATE";
+ /** See {@code Manifest#ACCESS_PDB_STATE} */
+ public static final String ACCESS_PDB_STATE = "android.permission.ACCESS_PDB_STATE";
+ /** See {@code Manifest#TEST_BLACKLISTED_PASSWORD} */
+ public static final String TEST_BLACKLISTED_PASSWORD =
+ "android.permission.TEST_BLACKLISTED_PASSWORD";
+ /** See {@code Manifest#NOTIFY_PENDING_SYSTEM_UPDATE} */
+ public static final String NOTIFY_PENDING_SYSTEM_UPDATE =
+ "android.permission.NOTIFY_PENDING_SYSTEM_UPDATE";
+ /** See {@code Manifest#CAMERA_DISABLE_TRANSMIT_LED} */
+ public static final String CAMERA_DISABLE_TRANSMIT_LED =
+ "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
+ /** See {@code Manifest#CAMERA_SEND_SYSTEM_EVENTS} */
+ public static final String CAMERA_SEND_SYSTEM_EVENTS =
+ "android.permission.CAMERA_SEND_SYSTEM_EVENTS";
+ /** See {@code Manifest#CAMERA_INJECT_EXTERNAL_CAMERA} */
+ public static final String CAMERA_INJECT_EXTERNAL_CAMERA =
+ "android.permission.CAMERA_INJECT_EXTERNAL_CAMERA";
+ /** See {@code Manifest#GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS} */
+ public static final String GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS = "android"
+ + ".permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS";
+ /** See {@code Manifest#MODIFY_PHONE_STATE} */
+ public static final String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
+ /** See {@code Manifest#READ_PRECISE_PHONE_STATE} */
+ public static final String READ_PRECISE_PHONE_STATE =
+ "android.permission.READ_PRECISE_PHONE_STATE";
+ /** See {@code Manifest#READ_PRIVILEGED_PHONE_STATE} */
+ public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission"
+ + ".READ_PRIVILEGED_PHONE_STATE";
+ /** See {@code Manifest#USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER} */
+ public static final String USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER =
+ "android.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER";
+ /** See {@code Manifest#READ_ACTIVE_EMERGENCY_SESSION} */
+ public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission"
+ + ".READ_ACTIVE_EMERGENCY_SESSION";
+ /** See {@code Manifest#LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH} */
+ public static final String LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH = "android.permission"
+ + ".LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH";
+ /** See {@code Manifest#REGISTER_SIM_SUBSCRIPTION} */
+ public static final String REGISTER_SIM_SUBSCRIPTION =
+ "android.permission.REGISTER_SIM_SUBSCRIPTION";
+ /** See {@code Manifest#REGISTER_CALL_PROVIDER} */
+ public static final String REGISTER_CALL_PROVIDER = "android.permission"
+ + ".REGISTER_CALL_PROVIDER";
+ /** See {@code Manifest#REGISTER_CONNECTION_MANAGER} */
+ public static final String REGISTER_CONNECTION_MANAGER =
+ "android.permission.REGISTER_CONNECTION_MANAGER";
+ /** See {@code Manifest#BIND_INCALL_SERVICE} */
+ public static final String BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE";
+ /** See {@code Manifest#MANAGE_ONGOING_CALLS} */
+ public static final String MANAGE_ONGOING_CALLS = "android.permission.MANAGE_ONGOING_CALLS";
+ /** See {@code Manifest#NETWORK_SCAN} */
+ public static final String NETWORK_SCAN = "android.permission.NETWORK_SCAN";
+ /** See {@code Manifest#BIND_VISUAL_VOICEMAIL_SERVICE} */
+ public static final String BIND_VISUAL_VOICEMAIL_SERVICE = "android.permission"
+ + ".BIND_VISUAL_VOICEMAIL_SERVICE";
+ /** See {@code Manifest#BIND_SCREENING_SERVICE} */
+ public static final String BIND_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE";
+ /** See {@code Manifest#BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE} */
+ public static final String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE =
+ "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE";
+ /** See {@code Manifest#BIND_CALL_DIAGNOSTIC_SERVICE} */
+ public static final String BIND_CALL_DIAGNOSTIC_SERVICE = "android.permission"
+ + ".BIND_CALL_DIAGNOSTIC_SERVICE";
+ /** See {@code Manifest#BIND_CALL_REDIRECTION_SERVICE} */
+ public static final String BIND_CALL_REDIRECTION_SERVICE =
+ "android.permission.BIND_CALL_REDIRECTION_SERVICE";
+ /** See {@code Manifest#BIND_CONNECTION_SERVICE} */
+ public static final String BIND_CONNECTION_SERVICE =
+ "android.permission.BIND_CONNECTION_SERVICE";
+ /** See {@code Manifest#BIND_TELECOM_CONNECTION_SERVICE} */
+ public static final String BIND_TELECOM_CONNECTION_SERVICE = "android.permission"
+ + ".BIND_TELECOM_CONNECTION_SERVICE";
+ /** See {@code Manifest#CONTROL_INCALL_EXPERIENCE} */
+ public static final String CONTROL_INCALL_EXPERIENCE = "android.permission"
+ + ".CONTROL_INCALL_EXPERIENCE";
+ /** See {@code Manifest#RECEIVE_STK_COMMANDS} */
+ public static final String RECEIVE_STK_COMMANDS = "android.permission.RECEIVE_STK_COMMANDS";
+ /** See {@code Manifest#SEND_EMBMS_INTENTS} */
+ public static final String SEND_EMBMS_INTENTS = "android.permission.SEND_EMBMS_INTENTS";
+ /** See {@code Manifest#MANAGE_SENSORS} */
+ public static final String MANAGE_SENSORS = "android.permission.MANAGE_SENSORS";
+ /** See {@code Manifest#BIND_IMS_SERVICE} */
+ public static final String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE";
+ /** See {@code Manifest#BIND_TELEPHONY_DATA_SERVICE} */
+ public static final String BIND_TELEPHONY_DATA_SERVICE =
+ "android.permission.BIND_TELEPHONY_DATA_SERVICE";
+ /** See {@code Manifest#BIND_TELEPHONY_NETWORK_SERVICE} */
+ public static final String BIND_TELEPHONY_NETWORK_SERVICE =
+ "android.permission.BIND_TELEPHONY_NETWORK_SERVICE";
+ /** See {@code Manifest#WRITE_EMBEDDED_SUBSCRIPTIONS} */
+ public static final String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission"
+ + ".WRITE_EMBEDDED_SUBSCRIPTIONS";
+ /** See {@code Manifest#BIND_EUICC_SERVICE} */
+ public static final String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE";
+ /** See {@code Manifest#READ_CARRIER_APP_INFO} */
+ public static final String READ_CARRIER_APP_INFO = "android.permission.READ_CARRIER_APP_INFO";
+ /** See {@code Manifest#BIND_GBA_SERVICE} */
+ public static final String BIND_GBA_SERVICE = "android.permission.BIND_GBA_SERVICE";
+ /** See {@code Manifest#ACCESS_RCS_USER_CAPABILITY_EXCHANGE} */
+ public static final String ACCESS_RCS_USER_CAPABILITY_EXCHANGE =
+ "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE";
+ /** See {@code Manifest#WRITE_MEDIA_STORAGE} */
+ public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
+ /** See {@code Manifest#MANAGE_DOCUMENTS} */
+ public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
+ /** See {@code Manifest#CACHE_CONTENT} */
+ public static final String CACHE_CONTENT = "android.permission.CACHE_CONTENT";
+ /** See {@code Manifest#ALLOCATE_AGGRESSIVE} */
+ public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE";
+ /** See {@code Manifest#USE_RESERVED_DISK} */
+ public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
+ /** See {@code Manifest#DISABLE_KEYGUARD} */
+ public static final String DISABLE_KEYGUARD = "android.permission.DISABLE_KEYGUARD";
+ /** See {@code Manifest#REQUEST_PASSWORD_COMPLEXITY} */
+ public static final String REQUEST_PASSWORD_COMPLEXITY =
+ "android.permission.REQUEST_PASSWORD_COMPLEXITY";
+ /** See {@code Manifest#GET_TASKS} */
+ public static final String GET_TASKS = "android.permission.GET_TASKS";
+ /** See {@code Manifest#REAL_GET_TASKS} */
+ public static final String REAL_GET_TASKS = "android.permission.REAL_GET_TASKS";
+ /** See {@code Manifest#START_TASKS_FROM_RECENTS} */
+ public static final String START_TASKS_FROM_RECENTS =
+ "android.permission.START_TASKS_FROM_RECENTS";
+ /** See {@code Manifest#INTERACT_ACROSS_USERS} */
+ public static final String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS";
+ /** See {@code Manifest#INTERACT_ACROSS_USERS_FULL} */
+ public static final String INTERACT_ACROSS_USERS_FULL =
+ "android.permission.INTERACT_ACROSS_USERS_FULL";
+ /** See {@code Manifest#INTERACT_ACROSS_PROFILES} */
+ public static final String INTERACT_ACROSS_PROFILES = "android.permission"
+ + ".INTERACT_ACROSS_PROFILES";
+ /** See {@code Manifest#CONFIGURE_INTERACT_ACROSS_PROFILES} */
+ public static final String CONFIGURE_INTERACT_ACROSS_PROFILES =
+ "android.permission.CONFIGURE_INTERACT_ACROSS_PROFILES";
+ /** See {@code Manifest#MANAGE_USERS} */
+ public static final String MANAGE_USERS = "android.permission.MANAGE_USERS";
+ /** See {@code Manifest#CREATE_USERS} */
+ public static final String CREATE_USERS = "android.permission.CREATE_USERS";
+ /** See {@code Manifest#QUERY_USERS} */
+ public static final String QUERY_USERS = "android.permission.QUERY_USERS";
+ /** See {@code Manifest#ACCESS_BLOBS_ACROSS_USERS} */
+ public static final String ACCESS_BLOBS_ACROSS_USERS = "android.permission"
+ + ".ACCESS_BLOBS_ACROSS_USERS";
+ /** See {@code Manifest#MANAGE_PROFILE_AND_DEVICE_OWNERS} */
+ public static final String MANAGE_PROFILE_AND_DEVICE_OWNERS = "android.permission"
+ + ".MANAGE_PROFILE_AND_DEVICE_OWNERS";
+ /** See {@code Manifest#QUERY_ADMIN_POLICY} */
+ public static final String QUERY_ADMIN_POLICY = "android.permission.QUERY_ADMIN_POLICY";
+ /** See {@code Manifest#CLEAR_FREEZE_PERIOD} */
+ public static final String CLEAR_FREEZE_PERIOD = "android.permission.CLEAR_FREEZE_PERIOD";
+ /** See {@code Manifest#FORCE_DEVICE_POLICY_MANAGER_LOGS} */
+ public static final String FORCE_DEVICE_POLICY_MANAGER_LOGS = "android.permission"
+ + ".FORCE_DEVICE_POLICY_MANAGER_LOGS";
+ /** See {@code Manifest#GET_DETAILED_TASKS} */
+ public static final String GET_DETAILED_TASKS = "android.permission.GET_DETAILED_TASKS";
+ /** See {@code Manifest#REORDER_TASKS} */
+ public static final String REORDER_TASKS = "android.permission.REORDER_TASKS";
+ /** See {@code Manifest#REMOVE_TASKS} */
+ public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
+ /** See {@code Manifest#MANAGE_ACTIVITY_STACKS} */
+ public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
+ /** See {@code Manifest#MANAGE_ACTIVITY_TASKS} */
+ public static final String MANAGE_ACTIVITY_TASKS = "android.permission.MANAGE_ACTIVITY_TASKS";
+ /** See {@code Manifest#ACTIVITY_EMBEDDING} */
+ public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
+ /** See {@code Manifest#START_ANY_ACTIVITY} */
+ public static final String START_ANY_ACTIVITY = "android.permission.START_ANY_ACTIVITY";
+ /** See {@code Manifest#START_ACTIVITIES_FROM_BACKGROUND} */
+ public static final String START_ACTIVITIES_FROM_BACKGROUND = "android.permission"
+ + ".START_ACTIVITIES_FROM_BACKGROUND";
+ /** See {@code Manifest#START_FOREGROUND_SERVICES_FROM_BACKGROUND} */
+ public static final String START_FOREGROUND_SERVICES_FROM_BACKGROUND = "android.permission"
+ + ".START_FOREGROUND_SERVICES_FROM_BACKGROUND";
+ /** See {@code Manifest#SEND_SHOW_SUSPENDED_APP_DETAILS} */
+ public static final String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission"
+ + ".SEND_SHOW_SUSPENDED_APP_DETAILS";
+ /** See {@code Manifest#START_ACTIVITY_AS_CALLER} */
+ public static final String START_ACTIVITY_AS_CALLER = "android.permission"
+ + ".START_ACTIVITY_AS_CALLER";
+ /** See {@code Manifest#RESTART_PACKAGES} */
+ public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+ /** See {@code Manifest#GET_PROCESS_STATE_AND_OOM_SCORE} */
+ public static final String GET_PROCESS_STATE_AND_OOM_SCORE =
+ "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE";
+ /** See {@code Manifest#GET_INTENT_SENDER_INTENT} */
+ public static final String GET_INTENT_SENDER_INTENT =
+ "android.permission.GET_INTENT_SENDER_INTENT";
+ /** See {@code Manifest#SYSTEM_ALERT_WINDOW} */
+ public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
+ /** See {@code Manifest#SYSTEM_APPLICATION_OVERLAY} */
+ public static final String SYSTEM_APPLICATION_OVERLAY =
+ "android.permission.SYSTEM_APPLICATION_OVERLAY";
+ /** See {@code Manifest#RUN_IN_BACKGROUND} */
+ public static final String RUN_IN_BACKGROUND = "android.permission.RUN_IN_BACKGROUND";
+ /** See {@code Manifest#USE_DATA_IN_BACKGROUND} */
+ public static final String USE_DATA_IN_BACKGROUND = "android.permission"
+ + ".USE_DATA_IN_BACKGROUND";
+ /** See {@code Manifest#SET_DISPLAY_OFFSET} */
+ public static final String SET_DISPLAY_OFFSET = "android.permission.SET_DISPLAY_OFFSET";
+ /** See {@code Manifest#REQUEST_COMPANION_RUN_IN_BACKGROUND} */
+ public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission"
+ + ".REQUEST_COMPANION_RUN_IN_BACKGROUND";
+ /** See {@code Manifest#REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND} */
+ public static final String REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND =
+ "android.permission.REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND";
+ /** See {@code Manifest#REQUEST_COMPANION_USE_DATA_IN_BACKGROUND} */
+ public static final String REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = "android.permission"
+ + ".REQUEST_COMPANION_USE_DATA_IN_BACKGROUND";
+ /** See {@code Manifest#REQUEST_COMPANION_PROFILE_WATCH} */
+ public static final String REQUEST_COMPANION_PROFILE_WATCH =
+ "android.permission.REQUEST_COMPANION_PROFILE_WATCH";
+ /** See {@code Manifest#REQUEST_COMPANION_PROFILE_APP_STREAMING} */
+ public static final String REQUEST_COMPANION_PROFILE_APP_STREAMING = "android.permission"
+ + ".REQUEST_COMPANION_PROFILE_APP_STREAMING";
+ /** See {@code Manifest#REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION} */
+ public static final String REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION =
+ "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION";
+ /** See {@code Manifest#REQUEST_COMPANION_SELF_MANAGED} */
+ public static final String REQUEST_COMPANION_SELF_MANAGED = "android.permission"
+ + ".REQUEST_COMPANION_SELF_MANAGED";
+ /** See {@code Manifest#COMPANION_APPROVE_WIFI_CONNECTIONS} */
+ public static final String COMPANION_APPROVE_WIFI_CONNECTIONS = "android.permission"
+ + ".COMPANION_APPROVE_WIFI_CONNECTIONS";
+ /** See {@code Manifest#READ_PROJECTION_STATE} */
+ public static final String READ_PROJECTION_STATE = "android.permission.READ_PROJECTION_STATE";
+ /** See {@code Manifest#TOGGLE_AUTOMOTIVE_PROJECTION} */
+ public static final String TOGGLE_AUTOMOTIVE_PROJECTION =
+ "android.permission.TOGGLE_AUTOMOTIVE_PROJECTION";
+ /** See {@code Manifest#HIDE_OVERLAY_WINDOWS} */
+ public static final String HIDE_OVERLAY_WINDOWS = "android.permission.HIDE_OVERLAY_WINDOWS";
+ /** See {@code Manifest#SET_WALLPAPER} */
+ public static final String SET_WALLPAPER = "android.permission.SET_WALLPAPER";
+ /** See {@code Manifest#SET_WALLPAPER_HINTS} */
+ public static final String SET_WALLPAPER_HINTS = "android.permission.SET_WALLPAPER_HINTS";
+ /** See {@code Manifest#READ_WALLPAPER_INTERNAL} */
+ public static final String READ_WALLPAPER_INTERNAL = "android.permission"
+ + ".READ_WALLPAPER_INTERNAL";
+ /** See {@code Manifest#SET_TIME} */
+ public static final String SET_TIME = "android.permission.SET_TIME";
+ /** See {@code Manifest#SET_TIME_ZONE} */
+ public static final String SET_TIME_ZONE = "android.permission.SET_TIME_ZONE";
+ /** See {@code Manifest#SUGGEST_TELEPHONY_TIME_AND_ZONE} */
+ public static final String SUGGEST_TELEPHONY_TIME_AND_ZONE =
+ "android.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE";
+ /** See {@code Manifest#SUGGEST_MANUAL_TIME_AND_ZONE} */
+ public static final String SUGGEST_MANUAL_TIME_AND_ZONE = "android.permission"
+ + ".SUGGEST_MANUAL_TIME_AND_ZONE";
+ /** See {@code Manifest#SUGGEST_EXTERNAL_TIME} */
+ public static final String SUGGEST_EXTERNAL_TIME = "android.permission.SUGGEST_EXTERNAL_TIME";
+ /** See {@code Manifest#MANAGE_TIME_AND_ZONE_DETECTION} */
+ public static final String MANAGE_TIME_AND_ZONE_DETECTION = "android.permission"
+ + ".MANAGE_TIME_AND_ZONE_DETECTION";
+ /** See {@code Manifest#EXPAND_STATUS_BAR} */
+ public static final String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
+ /** See {@code Manifest#INSTALL_SHORTCUT} */
+ public static final String INSTALL_SHORTCUT = "com.android.launcher.permission"
+ + ".INSTALL_SHORTCUT";
+ /** See {@code Manifest#READ_SYNC_SETTINGS} */
+ public static final String READ_SYNC_SETTINGS = "android.permission.READ_SYNC_SETTINGS";
+ /** See {@code Manifest#WRITE_SYNC_SETTINGS} */
+ public static final String WRITE_SYNC_SETTINGS = "android.permission.WRITE_SYNC_SETTINGS";
+ /** See {@code Manifest#READ_SYNC_STATS} */
+ public static final String READ_SYNC_STATS = "android.permission.READ_SYNC_STATS";
+ /** See {@code Manifest#SET_SCREEN_COMPATIBILITY} */
+ public static final String SET_SCREEN_COMPATIBILITY = "android.permission"
+ + ".SET_SCREEN_COMPATIBILITY";
+ /** See {@code Manifest#CHANGE_CONFIGURATION} */
+ public static final String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION";
+ /** See {@code Manifest#WRITE_GSERVICES} */
+ public static final String WRITE_GSERVICES = "android.permission.WRITE_GSERVICES";
+ /** See {@code Manifest#WRITE_DEVICE_CONFIG} */
+ public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
+ /** See {@code Manifest#READ_DEVICE_CONFIG} */
+ public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG";
+ /** See {@code Manifest#READ_APP_SPECIFIC_LOCALES} */
+ public static final String READ_APP_SPECIFIC_LOCALES =
+ "android.permission.READ_APP_SPECIFIC_LOCALES";
+ /** See {@code Manifest#MONITOR_DEVICE_CONFIG_ACCESS} */
+ public static final String MONITOR_DEVICE_CONFIG_ACCESS =
+ "android.permission.MONITOR_DEVICE_CONFIG_ACCESS";
+ /** See {@code Manifest#FORCE_STOP_PACKAGES} */
+ public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
+ /** See {@code Manifest#RETRIEVE_WINDOW_CONTENT} */
+ public static final String RETRIEVE_WINDOW_CONTENT =
+ "android.permission.RETRIEVE_WINDOW_CONTENT";
+ /** See {@code Manifest#SET_ANIMATION_SCALE} */
+ public static final String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE";
+ /** See {@code Manifest#PERSISTENT_ACTIVITY} */
+ public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY";
+ /** See {@code Manifest#GET_PACKAGE_SIZE} */
+ public static final String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
+ /** See {@code Manifest#RECEIVE_BOOT_COMPLETED} */
+ public static final String RECEIVE_BOOT_COMPLETED = "android.permission.RECEIVE_BOOT_COMPLETED";
+ /** See {@code Manifest#BROADCAST_STICKY} */
+ public static final String BROADCAST_STICKY = "android.permission.BROADCAST_STICKY";
+ /** See {@code Manifest#MOUNT_UNMOUNT_FILESYSTEMS} */
+ public static final String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission"
+ + ".MOUNT_UNMOUNT_FILESYSTEMS";
+ /** See {@code Manifest#MOUNT_FORMAT_FILESYSTEMS} */
+ public static final String MOUNT_FORMAT_FILESYSTEMS = "android.permission"
+ + ".MOUNT_FORMAT_FILESYSTEMS";
+ /** See {@code Manifest#STORAGE_INTERNAL} */
+ public static final String STORAGE_INTERNAL = "android.permission.STORAGE_INTERNAL";
+ /** See {@code Manifest#ASEC_ACCESS} */
+ public static final String ASEC_ACCESS = "android.permission.ASEC_ACCESS";
+ /** See {@code Manifest#ASEC_CREATE} */
+ public static final String ASEC_CREATE = "android.permission.ASEC_CREATE";
+ /** See {@code Manifest#ASEC_DESTROY} */
+ public static final String ASEC_DESTROY = "android.permission.ASEC_DESTROY";
+ /** See {@code Manifest#ASEC_MOUNT_UNMOUNT} */
+ public static final String ASEC_MOUNT_UNMOUNT = "android.permission.ASEC_MOUNT_UNMOUNT";
+ /** See {@code Manifest#ASEC_RENAME} */
+ public static final String ASEC_RENAME = "android.permission.ASEC_RENAME";
+ /** See {@code Manifest#WRITE_APN_SETTINGS} */
+ public static final String WRITE_APN_SETTINGS = "android.permission.WRITE_APN_SETTINGS";
+ /** See {@code Manifest#CHANGE_NETWORK_STATE} */
+ public static final String CHANGE_NETWORK_STATE = "android.permission.CHANGE_NETWORK_STATE";
+ /** See {@code Manifest#CLEAR_APP_CACHE} */
+ public static final String CLEAR_APP_CACHE = "android.permission.CLEAR_APP_CACHE";
+ /** See {@code Manifest#ALLOW_ANY_CODEC_FOR_PLAYBACK} */
+ public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission"
+ + ".ALLOW_ANY_CODEC_FOR_PLAYBACK";
+ /** See {@code Manifest#MANAGE_CA_CERTIFICATES} */
+ public static final String MANAGE_CA_CERTIFICATES = "android.permission"
+ + ".MANAGE_CA_CERTIFICATES";
+ /** See {@code Manifest#RECOVERY} */
+ public static final String RECOVERY = "android.permission.RECOVERY";
+ /** See {@code Manifest#BIND_RESUME_ON_REBOOT_SERVICE} */
+ public static final String BIND_RESUME_ON_REBOOT_SERVICE = "android.permission"
+ + ".BIND_RESUME_ON_REBOOT_SERVICE";
+ /** See {@code Manifest#READ_SYSTEM_UPDATE_INFO} */
+ public static final String READ_SYSTEM_UPDATE_INFO = "android.permission"
+ + ".READ_SYSTEM_UPDATE_INFO";
+ /** See {@code Manifest#BIND_JOB_SERVICE} */
+ public static final String BIND_JOB_SERVICE = "android.permission.BIND_JOB_SERVICE";
+ /** See {@code Manifest#UPDATE_CONFIG} */
+ public static final String UPDATE_CONFIG = "android.permission.UPDATE_CONFIG";
+ /** See {@code Manifest#QUERY_TIME_ZONE_RULES} */
+ public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
+ /** See {@code Manifest#UPDATE_TIME_ZONE_RULES} */
+ public static final String UPDATE_TIME_ZONE_RULES = "android.permission"
+ + ".UPDATE_TIME_ZONE_RULES";
+ /** See {@code Manifest#TRIGGER_TIME_ZONE_RULES_CHECK} */
+ public static final String TRIGGER_TIME_ZONE_RULES_CHECK = "android.permission"
+ + ".TRIGGER_TIME_ZONE_RULES_CHECK";
+ /** See {@code Manifest#RESET_SHORTCUT_MANAGER_THROTTLING} */
+ public static final String RESET_SHORTCUT_MANAGER_THROTTLING =
+ "android.permission.RESET_SHORTCUT_MANAGER_THROTTLING";
+ /** See {@code Manifest#BIND_NETWORK_RECOMMENDATION_SERVICE} */
+ public static final String BIND_NETWORK_RECOMMENDATION_SERVICE =
+ "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE";
+ /** See {@code Manifest#MANAGE_CREDENTIAL_MANAGEMENT_APP} */
+ public static final String MANAGE_CREDENTIAL_MANAGEMENT_APP = "android.permission"
+ + ".MANAGE_CREDENTIAL_MANAGEMENT_APP";
+ /** See {@code Manifest#UPDATE_FONTS} */
+ public static final String UPDATE_FONTS = "android.permission.UPDATE_FONTS";
+ /** See {@code Manifest#USE_ATTESTATION_VERIFICATION_SERVICE} */
+ public static final String USE_ATTESTATION_VERIFICATION_SERVICE =
+ "android.permission.USE_ATTESTATION_VERIFICATION_SERVICE";
+ /** See {@code Manifest#VERIFY_ATTESTATION} */
+ public static final String VERIFY_ATTESTATION = "android.permission.VERIFY_ATTESTATION";
+ /** See {@code Manifest#BIND_ATTESTATION_VERIFICATION_SERVICE} */
+ public static final String BIND_ATTESTATION_VERIFICATION_SERVICE = "android.permission"
+ + ".BIND_ATTESTATION_VERIFICATION_SERVICE";
+ /** See {@code Manifest#WRITE_SECURE_SETTINGS} */
+ public static final String WRITE_SECURE_SETTINGS = "android.permission.WRITE_SECURE_SETTINGS";
+ /** See {@code Manifest#DUMP} */
+ public static final String DUMP = "android.permission.DUMP";
+ /** See {@code Manifest#CONTROL_UI_TRACING} */
+ public static final String CONTROL_UI_TRACING = "android.permission.CONTROL_UI_TRACING";
+ /** See {@code Manifest#READ_LOGS} */
+ public static final String READ_LOGS = "android.permission.READ_LOGS";
+ /** See {@code Manifest#SET_DEBUG_APP} */
+ public static final String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP";
+ /** See {@code Manifest#SET_PROCESS_LIMIT} */
+ public static final String SET_PROCESS_LIMIT = "android.permission.SET_PROCESS_LIMIT";
+ /** See {@code Manifest#SET_ALWAYS_FINISH} */
+ public static final String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH";
+ /** See {@code Manifest#SIGNAL_PERSISTENT_PROCESSES} */
+ public static final String SIGNAL_PERSISTENT_PROCESSES =
+ "android.permission.SIGNAL_PERSISTENT_PROCESSES";
+ /** See {@code Manifest#APPROVE_INCIDENT_REPORTS} */
+ public static final String APPROVE_INCIDENT_REPORTS =
+ "android.permission.APPROVE_INCIDENT_REPORTS";
+ /** See {@code Manifest#REQUEST_INCIDENT_REPORT_APPROVAL} */
+ public static final String REQUEST_INCIDENT_REPORT_APPROVAL = "android.permission"
+ + ".REQUEST_INCIDENT_REPORT_APPROVAL";
+ /** See {@code Manifest#GET_ACCOUNTS_PRIVILEGED} */
+ public static final String GET_ACCOUNTS_PRIVILEGED = "android.permission"
+ + ".GET_ACCOUNTS_PRIVILEGED";
+ /** See {@code Manifest#GET_PASSWORD} */
+ public static final String GET_PASSWORD = "android.permission.GET_PASSWORD";
+ /** See {@code Manifest#DIAGNOSTIC} */
+ public static final String DIAGNOSTIC = "android.permission.DIAGNOSTIC";
+ /** See {@code Manifest#STATUS_BAR} */
+ public static final String STATUS_BAR = "android.permission.STATUS_BAR";
+ /** See {@code Manifest#TRIGGER_SHELL_BUGREPORT} */
+ public static final String TRIGGER_SHELL_BUGREPORT = "android.permission"
+ + ".TRIGGER_SHELL_BUGREPORT";
+ /** See {@code Manifest#TRIGGER_SHELL_PROFCOLLECT_UPLOAD} */
+ public static final String TRIGGER_SHELL_PROFCOLLECT_UPLOAD = "android.permission"
+ + ".TRIGGER_SHELL_PROFCOLLECT_UPLOAD";
+ /** See {@code Manifest#STATUS_BAR_SERVICE} */
+ public static final String STATUS_BAR_SERVICE = "android.permission.STATUS_BAR_SERVICE";
+ /** See {@code Manifest#BIND_QUICK_SETTINGS_TILE} */
+ public static final String BIND_QUICK_SETTINGS_TILE =
+ "android.permission.BIND_QUICK_SETTINGS_TILE";
+ /** See {@code Manifest#BIND_CONTROLS} */
+ public static final String BIND_CONTROLS = "android.permission.BIND_CONTROLS";
+ /** See {@code Manifest#FORCE_BACK} */
+ public static final String FORCE_BACK = "android.permission.FORCE_BACK";
+ /** See {@code Manifest#UPDATE_DEVICE_STATS} */
+ public static final String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS";
+ /** See {@code Manifest#GET_APP_OPS_STATS} */
+ public static final String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS";
+ /** See {@code Manifest#GET_HISTORICAL_APP_OPS_STATS} */
+ public static final String GET_HISTORICAL_APP_OPS_STATS = "android.permission"
+ + ".GET_HISTORICAL_APP_OPS_STATS";
+ /** See {@code Manifest#UPDATE_APP_OPS_STATS} */
+ public static final String UPDATE_APP_OPS_STATS = "android.permission.UPDATE_APP_OPS_STATS";
+ /** See {@code Manifest#MANAGE_APP_OPS_RESTRICTIONS} */
+ public static final String MANAGE_APP_OPS_RESTRICTIONS = "android.permission"
+ + ".MANAGE_APP_OPS_RESTRICTIONS";
+ /** See {@code Manifest#MANAGE_APP_OPS_MODES} */
+ public static final String MANAGE_APP_OPS_MODES = "android.permission.MANAGE_APP_OPS_MODES";
+ /** See {@code Manifest#INTERNAL_SYSTEM_WINDOW} */
+ public static final String INTERNAL_SYSTEM_WINDOW = "android.permission"
+ + ".INTERNAL_SYSTEM_WINDOW";
+ /** See {@code Manifest#UNLIMITED_TOASTS} */
+ public static final String UNLIMITED_TOASTS = "android.permission.UNLIMITED_TOASTS";
+ /** See {@code Manifest#HIDE_NON_SYSTEM_OVERLAY_WINDOWS} */
+ public static final String HIDE_NON_SYSTEM_OVERLAY_WINDOWS =
+ "android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS";
+ /** See {@code Manifest#MANAGE_APP_TOKENS} */
+ public static final String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS";
+ /** See {@code Manifest#REGISTER_WINDOW_MANAGER_LISTENERS} */
+ public static final String REGISTER_WINDOW_MANAGER_LISTENERS = "android.permission"
+ + ".REGISTER_WINDOW_MANAGER_LISTENERS";
+ /** See {@code Manifest#FREEZE_SCREEN} */
+ public static final String FREEZE_SCREEN = "android.permission.FREEZE_SCREEN";
+ /** See {@code Manifest#INJECT_EVENTS} */
+ public static final String INJECT_EVENTS = "android.permission.INJECT_EVENTS";
+ /** See {@code Manifest#FILTER_EVENTS} */
+ public static final String FILTER_EVENTS = "android.permission.FILTER_EVENTS";
+ /** See {@code Manifest#RETRIEVE_WINDOW_TOKEN} */
+ public static final String RETRIEVE_WINDOW_TOKEN = "android.permission.RETRIEVE_WINDOW_TOKEN";
+ /** See {@code Manifest#MODIFY_ACCESSIBILITY_DATA} */
+ public static final String MODIFY_ACCESSIBILITY_DATA =
+ "android.permission.MODIFY_ACCESSIBILITY_DATA";
+ /** See {@code Manifest#ACT_AS_PACKAGE_FOR_ACCESSIBILITY} */
+ public static final String ACT_AS_PACKAGE_FOR_ACCESSIBILITY = "android.permission"
+ + ".ACT_AS_PACKAGE_FOR_ACCESSIBILITY";
+ /** See {@code Manifest#CHANGE_ACCESSIBILITY_VOLUME} */
+ public static final String CHANGE_ACCESSIBILITY_VOLUME =
+ "android.permission.CHANGE_ACCESSIBILITY_VOLUME";
+ /** See {@code Manifest#FRAME_STATS} */
+ public static final String FRAME_STATS = "android.permission.FRAME_STATS";
+ /** See {@code Manifest#TEMPORARY_ENABLE_ACCESSIBILITY} */
+ public static final String TEMPORARY_ENABLE_ACCESSIBILITY = "android.permission"
+ + ".TEMPORARY_ENABLE_ACCESSIBILITY";
+ /** See {@code Manifest#OPEN_ACCESSIBILITY_DETAILS_SETTINGS} */
+ public static final String OPEN_ACCESSIBILITY_DETAILS_SETTINGS = "android.permission"
+ + ".OPEN_ACCESSIBILITY_DETAILS_SETTINGS";
+ /** See {@code Manifest#SET_ACTIVITY_WATCHER} */
+ public static final String SET_ACTIVITY_WATCHER = "android.permission.SET_ACTIVITY_WATCHER";
+ /** See {@code Manifest#SHUTDOWN} */
+ public static final String SHUTDOWN = "android.permission.SHUTDOWN";
+ /** See {@code Manifest#STOP_APP_SWITCHES} */
+ public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
+ /** See {@code Manifest#GET_TOP_ACTIVITY_INFO} */
+ public static final String GET_TOP_ACTIVITY_INFO = "android.permission.GET_TOP_ACTIVITY_INFO";
+ /** See {@code Manifest#READ_INPUT_STATE} */
+ public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE";
+ /** See {@code Manifest#BIND_INPUT_METHOD} */
+ public static final String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
+ /** See {@code Manifest#BIND_MIDI_DEVICE_SERVICE} */
+ public static final String BIND_MIDI_DEVICE_SERVICE = "android.permission"
+ + ".BIND_MIDI_DEVICE_SERVICE";
+ /** See {@code Manifest#BIND_ACCESSIBILITY_SERVICE} */
+ public static final String BIND_ACCESSIBILITY_SERVICE =
+ "android.permission.BIND_ACCESSIBILITY_SERVICE";
+ /** See {@code Manifest#BIND_PRINT_SERVICE} */
+ public static final String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
+ /** See {@code Manifest#BIND_PRINT_RECOMMENDATION_SERVICE} */
+ public static final String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE";
+ /** See {@code Manifest#READ_PRINT_SERVICES} */
+ public static final String READ_PRINT_SERVICES = "android.permission.READ_PRINT_SERVICES";
+ /** See {@code Manifest#READ_PRINT_SERVICE_RECOMMENDATIONS} */
+ public static final String READ_PRINT_SERVICE_RECOMMENDATIONS = "android.permission"
+ + ".READ_PRINT_SERVICE_RECOMMENDATIONS";
+ /** See {@code Manifest#BIND_NFC_SERVICE} */
+ public static final String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
+ /** See {@code Manifest#BIND_QUICK_ACCESS_WALLET_SERVICE} */
+ public static final String BIND_QUICK_ACCESS_WALLET_SERVICE =
+ "android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE";
+ /** See {@code Manifest#BIND_PRINT_SPOOLER_SERVICE} */
+ public static final String BIND_PRINT_SPOOLER_SERVICE = "android.permission"
+ + ".BIND_PRINT_SPOOLER_SERVICE";
+ /** See {@code Manifest#BIND_COMPANION_DEVICE_MANAGER_SERVICE} */
+ public static final String BIND_COMPANION_DEVICE_MANAGER_SERVICE =
+ "android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE";
+ /** See {@code Manifest#BIND_COMPANION_DEVICE_SERVICE} */
+ public static final String BIND_COMPANION_DEVICE_SERVICE = "android.permission"
+ + ".BIND_COMPANION_DEVICE_SERVICE";
+ /** See {@code Manifest#BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE} */
+ public static final String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission"
+ + ".BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE";
+ /** See {@code Manifest#BIND_TEXT_SERVICE} */
+ public static final String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
+ /** See {@code Manifest#BIND_ATTENTION_SERVICE} */
+ public static final String BIND_ATTENTION_SERVICE = "android.permission"
+ + ".BIND_ATTENTION_SERVICE";
+ /** See {@code Manifest#BIND_ROTATION_RESOLVER_SERVICE} */
+ public static final String BIND_ROTATION_RESOLVER_SERVICE = "android.permission"
+ + ".BIND_ROTATION_RESOLVER_SERVICE";
+ /** See {@code Manifest#BIND_VPN_SERVICE} */
+ public static final String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE";
+ /** See {@code Manifest#BIND_WALLPAPER} */
+ public static final String BIND_WALLPAPER = "android.permission.BIND_WALLPAPER";
+ /** See {@code Manifest#BIND_GAME_SERVICE} */
+ public static final String BIND_GAME_SERVICE = "android.permission.BIND_GAME_SERVICE";
+ /** See {@code Manifest#BIND_VOICE_INTERACTION} */
+ public static final String BIND_VOICE_INTERACTION = "android.permission"
+ + ".BIND_VOICE_INTERACTION";
+ /** See {@code Manifest#BIND_HOTWORD_DETECTION_SERVICE} */
+ public static final String BIND_HOTWORD_DETECTION_SERVICE = "android.permission"
+ + ".BIND_HOTWORD_DETECTION_SERVICE";
+ /** See {@code Manifest#MANAGE_HOTWORD_DETECTION} */
+ public static final String MANAGE_HOTWORD_DETECTION = "android.permission"
+ + ".MANAGE_HOTWORD_DETECTION";
+ /** See {@code Manifest#BIND_AUTOFILL_SERVICE} */
+ public static final String BIND_AUTOFILL_SERVICE = "android.permission.BIND_AUTOFILL_SERVICE";
+ /** See {@code Manifest#BIND_AUTOFILL} */
+ public static final String BIND_AUTOFILL = "android.permission.BIND_AUTOFILL";
+ /** See {@code Manifest#BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE} */
+ public static final String BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE = "android.permission"
+ + ".BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE";
+ /** See {@code Manifest#BIND_INLINE_SUGGESTION_RENDER_SERVICE} */
+ public static final String BIND_INLINE_SUGGESTION_RENDER_SERVICE =
+ "android.permission.BIND_INLINE_SUGGESTION_RENDER_SERVICE";
+ /** See {@code Manifest#BIND_TEXTCLASSIFIER_SERVICE} */
+ public static final String BIND_TEXTCLASSIFIER_SERVICE =
+ "android.permission.BIND_TEXTCLASSIFIER_SERVICE";
+ /** See {@code Manifest#BIND_CONTENT_CAPTURE_SERVICE} */
+ public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission"
+ + ".BIND_CONTENT_CAPTURE_SERVICE";
+ /** See {@code Manifest#BIND_TRANSLATION_SERVICE} */
+ public static final String BIND_TRANSLATION_SERVICE =
+ "android.permission.BIND_TRANSLATION_SERVICE";
+ /** See {@code Manifest#MANAGE_UI_TRANSLATION} */
+ public static final String MANAGE_UI_TRANSLATION = "android.permission.MANAGE_UI_TRANSLATION";
+ /** See {@code Manifest#BIND_CONTENT_SUGGESTIONS_SERVICE} */
+ public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission"
+ + ".BIND_CONTENT_SUGGESTIONS_SERVICE";
+ /** See {@code Manifest#BIND_MUSIC_RECOGNITION_SERVICE} */
+ public static final String BIND_MUSIC_RECOGNITION_SERVICE =
+ "android.permission.BIND_MUSIC_RECOGNITION_SERVICE";
+ /** See {@code Manifest#BIND_AUGMENTED_AUTOFILL_SERVICE} */
+ public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission"
+ + ".BIND_AUGMENTED_AUTOFILL_SERVICE";
+ /** See {@code Manifest#MANAGE_VOICE_KEYPHRASES} */
+ public static final String MANAGE_VOICE_KEYPHRASES = "android.permission"
+ + ".MANAGE_VOICE_KEYPHRASES";
+ /** See {@code Manifest#KEYPHRASE_ENROLLMENT_APPLICATION} */
+ public static final String KEYPHRASE_ENROLLMENT_APPLICATION =
+ "android.permission.KEYPHRASE_ENROLLMENT_APPLICATION";
+ /** See {@code Manifest#BIND_REMOTE_DISPLAY} */
+ public static final String BIND_REMOTE_DISPLAY = "android.permission.BIND_REMOTE_DISPLAY";
+ /** See {@code Manifest#BIND_TV_INPUT} */
+ public static final String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
+ /** See {@code Manifest#BIND_TV_REMOTE_SERVICE} */
+ public static final String BIND_TV_REMOTE_SERVICE = "android.permission"
+ + ".BIND_TV_REMOTE_SERVICE";
+ /** See {@code Manifest#TV_VIRTUAL_REMOTE_CONTROLLER} */
+ public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission"
+ + ".TV_VIRTUAL_REMOTE_CONTROLLER";
+ /** See {@code Manifest#CHANGE_HDMI_CEC_ACTIVE_SOURCE} */
+ public static final String CHANGE_HDMI_CEC_ACTIVE_SOURCE = "android.permission"
+ + ".CHANGE_HDMI_CEC_ACTIVE_SOURCE";
+ /** See {@code Manifest#MODIFY_PARENTAL_CONTROLS} */
+ public static final String MODIFY_PARENTAL_CONTROLS = "android.permission"
+ + ".MODIFY_PARENTAL_CONTROLS";
+ /** See {@code Manifest#READ_CONTENT_RATING_SYSTEMS} */
+ public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission"
+ + ".READ_CONTENT_RATING_SYSTEMS";
+ /** See {@code Manifest#NOTIFY_TV_INPUTS} */
+ public static final String NOTIFY_TV_INPUTS = "android.permission.NOTIFY_TV_INPUTS";
+ /** See {@code Manifest#TUNER_RESOURCE_ACCESS} */
+ public static final String TUNER_RESOURCE_ACCESS = "android.permission.TUNER_RESOURCE_ACCESS";
+ /** See {@code Manifest#MEDIA_RESOURCE_OVERRIDE_PID} */
+ public static final String MEDIA_RESOURCE_OVERRIDE_PID = "android.permission"
+ + ".MEDIA_RESOURCE_OVERRIDE_PID";
+ /** See {@code Manifest#REGISTER_MEDIA_RESOURCE_OBSERVER} */
+ public static final String REGISTER_MEDIA_RESOURCE_OBSERVER = "android.permission"
+ + ".REGISTER_MEDIA_RESOURCE_OBSERVER";
+ /** See {@code Manifest#BIND_ROUTE_PROVIDER} */
+ public static final String BIND_ROUTE_PROVIDER = "android.permission.BIND_ROUTE_PROVIDER";
+ /** See {@code Manifest#BIND_DEVICE_ADMIN} */
+ public static final String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
+ /** See {@code Manifest#MANAGE_DEVICE_ADMINS} */
+ public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
+ /** See {@code Manifest#RESET_PASSWORD} */
+ public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
+ /** See {@code Manifest#LOCK_DEVICE} */
+ public static final String LOCK_DEVICE = "android.permission.LOCK_DEVICE";
+ /** See {@code Manifest#SET_ORIENTATION} */
+ public static final String SET_ORIENTATION = "android.permission.SET_ORIENTATION";
+ /** See {@code Manifest#SET_POINTER_SPEED} */
+ public static final String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED";
+ /** See {@code Manifest#SET_INPUT_CALIBRATION} */
+ public static final String SET_INPUT_CALIBRATION = "android.permission.SET_INPUT_CALIBRATION";
+ /** See {@code Manifest#SET_KEYBOARD_LAYOUT} */
+ public static final String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT";
+ /** See {@code Manifest#SCHEDULE_PRIORITIZED_ALARM} */
+ public static final String SCHEDULE_PRIORITIZED_ALARM =
+ "android.permission.SCHEDULE_PRIORITIZED_ALARM";
+ /** See {@code Manifest#SCHEDULE_EXACT_ALARM} */
+ public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
+ /** See {@code Manifest#TABLET_MODE} */
+ public static final String TABLET_MODE = "android.permission.TABLET_MODE";
+ /** See {@code Manifest#REQUEST_INSTALL_PACKAGES} */
+ public static final String REQUEST_INSTALL_PACKAGES = "android.permission"
+ + ".REQUEST_INSTALL_PACKAGES";
+ /** See {@code Manifest#REQUEST_DELETE_PACKAGES} */
+ public static final String REQUEST_DELETE_PACKAGES = "android.permission"
+ + ".REQUEST_DELETE_PACKAGES";
+ /** See {@code Manifest#INSTALL_PACKAGES} */
+ public static final String INSTALL_PACKAGES = "android.permission.INSTALL_PACKAGES";
+ /** See {@code Manifest#INSTALL_SELF_UPDATES} */
+ public static final String INSTALL_SELF_UPDATES = "android.permission.INSTALL_SELF_UPDATES";
+ /** See {@code Manifest#INSTALL_PACKAGE_UPDATES} */
+ public static final String INSTALL_PACKAGE_UPDATES = "android.permission"
+ + ".INSTALL_PACKAGE_UPDATES";
+ /** See {@code Manifest#INSTALL_EXISTING_PACKAGES} */
+ public static final String INSTALL_EXISTING_PACKAGES = "com.android.permission"
+ + ".INSTALL_EXISTING_PACKAGES";
+ /** See {@code Manifest#USE_INSTALLER_V2} */
+ public static final String USE_INSTALLER_V2 = "com.android.permission.USE_INSTALLER_V2";
+ /** See {@code Manifest#INSTALL_TEST_ONLY_PACKAGE} */
+ public static final String INSTALL_TEST_ONLY_PACKAGE = "android.permission"
+ + ".INSTALL_TEST_ONLY_PACKAGE";
+ /** See {@code Manifest#INSTALL_DPC_PACKAGES} */
+ public static final String INSTALL_DPC_PACKAGES = "android.permission.INSTALL_DPC_PACKAGES";
+ /** See {@code Manifest#USE_SYSTEM_DATA_LOADERS} */
+ public static final String USE_SYSTEM_DATA_LOADERS = "com.android.permission"
+ + ".USE_SYSTEM_DATA_LOADERS";
+ /** See {@code Manifest#CLEAR_APP_USER_DATA} */
+ public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
+ /** See {@code Manifest#GET_APP_GRANTED_URI_PERMISSIONS} */
+ public static final String GET_APP_GRANTED_URI_PERMISSIONS = "android.permission"
+ + ".GET_APP_GRANTED_URI_PERMISSIONS";
+ /** See {@code Manifest#CLEAR_APP_GRANTED_URI_PERMISSIONS} */
+ public static final String CLEAR_APP_GRANTED_URI_PERMISSIONS =
+ "android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS";
+ /** See {@code Manifest#MANAGE_SCOPED_ACCESS_DIRECTORY_PERMISSIONS} */
+ public static final String MANAGE_SCOPED_ACCESS_DIRECTORY_PERMISSIONS =
+ "android.permission.MANAGE_SCOPED_ACCESS_DIRECTORY_PERMISSIONS";
+ /** See {@code Manifest#FORCE_PERSISTABLE_URI_PERMISSIONS} */
+ public static final String FORCE_PERSISTABLE_URI_PERMISSIONS = "android.permission"
+ + ".FORCE_PERSISTABLE_URI_PERMISSIONS";
+ /** See {@code Manifest#DELETE_CACHE_FILES} */
+ public static final String DELETE_CACHE_FILES = "android.permission.DELETE_CACHE_FILES";
+ /** See {@code Manifest#INTERNAL_DELETE_CACHE_FILES} */
+ public static final String INTERNAL_DELETE_CACHE_FILES =
+ "android.permission.INTERNAL_DELETE_CACHE_FILES";
+ /** See {@code Manifest#DELETE_PACKAGES} */
+ public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
+ /** See {@code Manifest#MOVE_PACKAGE} */
+ public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
+ /** See {@code Manifest#KEEP_UNINSTALLED_PACKAGES} */
+ public static final String KEEP_UNINSTALLED_PACKAGES =
+ "android.permission.KEEP_UNINSTALLED_PACKAGES";
+ /** See {@code Manifest#CHANGE_COMPONENT_ENABLED_STATE} */
+ public static final String CHANGE_COMPONENT_ENABLED_STATE =
+ "android.permission.CHANGE_COMPONENT_ENABLED_STATE";
+ /** See {@code Manifest#GRANT_RUNTIME_PERMISSIONS} */
+ public static final String GRANT_RUNTIME_PERMISSIONS =
+ "android.permission.GRANT_RUNTIME_PERMISSIONS";
+ /** See {@code Manifest#INSTALL_GRANT_RUNTIME_PERMISSIONS} */
+ public static final String INSTALL_GRANT_RUNTIME_PERMISSIONS = "android.permission"
+ + ".INSTALL_GRANT_RUNTIME_PERMISSIONS";
+ /** See {@code Manifest#REVOKE_RUNTIME_PERMISSIONS} */
+ public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission"
+ + ".REVOKE_RUNTIME_PERMISSIONS";
+ /** See {@code Manifest#GET_RUNTIME_PERMISSIONS} */
+ public static final String GET_RUNTIME_PERMISSIONS = "android.permission"
+ + ".GET_RUNTIME_PERMISSIONS";
+ /** See {@code Manifest#RESTORE_RUNTIME_PERMISSIONS} */
+ public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission"
+ + ".RESTORE_RUNTIME_PERMISSIONS";
+ /** See {@code Manifest#ADJUST_RUNTIME_PERMISSIONS_POLICY} */
+ public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission"
+ + ".ADJUST_RUNTIME_PERMISSIONS_POLICY";
+ /** See {@code Manifest#UPGRADE_RUNTIME_PERMISSIONS} */
+ public static final String UPGRADE_RUNTIME_PERMISSIONS =
+ "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
+ /** See {@code Manifest#WHITELIST_RESTRICTED_PERMISSIONS} */
+ public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission"
+ + ".WHITELIST_RESTRICTED_PERMISSIONS";
+ /** See {@code Manifest#WHITELIST_AUTO_REVOKE_PERMISSIONS} */
+ public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission"
+ + ".WHITELIST_AUTO_REVOKE_PERMISSIONS";
+ /** See {@code Manifest#OBSERVE_GRANT_REVOKE_PERMISSIONS} */
+ public static final String OBSERVE_GRANT_REVOKE_PERMISSIONS =
+ "android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS";
+ /** See {@code Manifest#MANAGE_ONE_TIME_PERMISSION_SESSIONS} */
+ public static final String MANAGE_ONE_TIME_PERMISSION_SESSIONS =
+ "android.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS";
+ /** See {@code Manifest#MANAGE_ROLE_HOLDERS} */
+ public static final String MANAGE_ROLE_HOLDERS = "android.permission.MANAGE_ROLE_HOLDERS";
+ /** See {@code Manifest#BYPASS_ROLE_QUALIFICATION} */
+ public static final String BYPASS_ROLE_QUALIFICATION = "android.permission"
+ + ".BYPASS_ROLE_QUALIFICATION";
+ /** See {@code Manifest#OBSERVE_ROLE_HOLDERS} */
+ public static final String OBSERVE_ROLE_HOLDERS = "android.permission.OBSERVE_ROLE_HOLDERS";
+ /** See {@code Manifest#MANAGE_COMPANION_DEVICES} */
+ public static final String MANAGE_COMPANION_DEVICES =
+ "android.permission.MANAGE_COMPANION_DEVICES";
+ /** See {@code Manifest#REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE} */
+ public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE =
+ "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE";
+ /** See {@code Manifest#DELIVER_COMPANION_MESSAGES} */
+ public static final String DELIVER_COMPANION_MESSAGES = "android.permission"
+ + ".DELIVER_COMPANION_MESSAGES";
+ /** See {@code Manifest#ACCESS_SURFACE_FLINGER} */
+ public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
+ /** See {@code Manifest#ROTATE_SURFACE_FLINGER} */
+ public static final String ROTATE_SURFACE_FLINGER = "android.permission"
+ + ".ROTATE_SURFACE_FLINGER";
+ /** See {@code Manifest#READ_FRAME_BUFFER} */
+ public static final String READ_FRAME_BUFFER = "android.permission.READ_FRAME_BUFFER";
+ /** See {@code Manifest#ACCESS_INPUT_FLINGER} */
+ public static final String ACCESS_INPUT_FLINGER = "android.permission.ACCESS_INPUT_FLINGER";
+ /** See {@code Manifest#DISABLE_INPUT_DEVICE} */
+ public static final String DISABLE_INPUT_DEVICE = "android.permission.DISABLE_INPUT_DEVICE";
+ /** See {@code Manifest#CONFIGURE_WIFI_DISPLAY} */
+ public static final String CONFIGURE_WIFI_DISPLAY =
+ "android.permission.CONFIGURE_WIFI_DISPLAY";
+ /** See {@code Manifest#CONTROL_WIFI_DISPLAY} */
+ public static final String CONTROL_WIFI_DISPLAY = "android.permission.CONTROL_WIFI_DISPLAY";
+ /** See {@code Manifest#CONFIGURE_DISPLAY_COLOR_MODE} */
+ public static final String CONFIGURE_DISPLAY_COLOR_MODE =
+ "android.permission.CONFIGURE_DISPLAY_COLOR_MODE";
+ /** See {@code Manifest#CONTROL_DEVICE_LIGHTS} */
+ public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS";
+ /** See {@code Manifest#CONTROL_DISPLAY_SATURATION} */
+ public static final String CONTROL_DISPLAY_SATURATION = "android.permission"
+ + ".CONTROL_DISPLAY_SATURATION";
+ /** See {@code Manifest#CONTROL_DISPLAY_COLOR_TRANSFORMS} */
+ public static final String CONTROL_DISPLAY_COLOR_TRANSFORMS =
+ "android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS";
+ /** See {@code Manifest#BRIGHTNESS_SLIDER_USAGE} */
+ public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission"
+ + ".BRIGHTNESS_SLIDER_USAGE";
+ /** See {@code Manifest#ACCESS_AMBIENT_LIGHT_STATS} */
+ public static final String ACCESS_AMBIENT_LIGHT_STATS =
+ "android.permission.ACCESS_AMBIENT_LIGHT_STATS";
+ /** See {@code Manifest#CONFIGURE_DISPLAY_BRIGHTNESS} */
+ public static final String CONFIGURE_DISPLAY_BRIGHTNESS =
+ "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
+ /** See {@code Manifest#CONTROL_DISPLAY_BRIGHTNESS} */
+ public static final String CONTROL_DISPLAY_BRIGHTNESS = "android.permission"
+ + ".CONTROL_DISPLAY_BRIGHTNESS";
+ /** See {@code Manifest#OVERRIDE_DISPLAY_MODE_REQUESTS} */
+ public static final String OVERRIDE_DISPLAY_MODE_REQUESTS = "android.permission"
+ + ".OVERRIDE_DISPLAY_MODE_REQUESTS";
+ /** See {@code Manifest#MODIFY_REFRESH_RATE_SWITCHING_TYPE} */
+ public static final String MODIFY_REFRESH_RATE_SWITCHING_TYPE =
+ "android.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE";
+ /** See {@code Manifest#CONTROL_VPN} */
+ public static final String CONTROL_VPN = "android.permission.CONTROL_VPN";
+ /** See {@code Manifest#CONTROL_ALWAYS_ON_VPN} */
+ public static final String CONTROL_ALWAYS_ON_VPN = "android.permission.CONTROL_ALWAYS_ON_VPN";
+ /** See {@code Manifest#CAPTURE_TUNER_AUDIO_INPUT} */
+ public static final String CAPTURE_TUNER_AUDIO_INPUT =
+ "android.permission.CAPTURE_TUNER_AUDIO_INPUT";
+ /** See {@code Manifest#CAPTURE_AUDIO_OUTPUT} */
+ public static final String CAPTURE_AUDIO_OUTPUT = "android.permission.CAPTURE_AUDIO_OUTPUT";
+ /** See {@code Manifest#CAPTURE_MEDIA_OUTPUT} */
+ public static final String CAPTURE_MEDIA_OUTPUT = "android.permission.CAPTURE_MEDIA_OUTPUT";
+ /** See {@code Manifest#CAPTURE_VOICE_COMMUNICATION_OUTPUT} */
+ public static final String CAPTURE_VOICE_COMMUNICATION_OUTPUT = "android.permission"
+ + ".CAPTURE_VOICE_COMMUNICATION_OUTPUT";
+ /** See {@code Manifest#CAPTURE_AUDIO_HOTWORD} */
+ public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
+ /** See {@code Manifest#SOUNDTRIGGER_DELEGATE_IDENTITY} */
+ public static final String SOUNDTRIGGER_DELEGATE_IDENTITY =
+ "android.permission.SOUNDTRIGGER_DELEGATE_IDENTITY";
+ /** See {@code Manifest#MODIFY_AUDIO_ROUTING} */
+ public static final String MODIFY_AUDIO_ROUTING = "android.permission.MODIFY_AUDIO_ROUTING";
+ /** See {@code Manifest#CALL_AUDIO_INTERCEPTION} */
+ public static final String CALL_AUDIO_INTERCEPTION =
+ "android.permission.CALL_AUDIO_INTERCEPTION";
+ /** See {@code Manifest#QUERY_AUDIO_STATE} */
+ public static final String QUERY_AUDIO_STATE = "android.permission.QUERY_AUDIO_STATE";
+ /** See {@code Manifest#MODIFY_DEFAULT_AUDIO_EFFECTS} */
+ public static final String MODIFY_DEFAULT_AUDIO_EFFECTS = "android.permission"
+ + ".MODIFY_DEFAULT_AUDIO_EFFECTS";
+ /** See {@code Manifest#DISABLE_SYSTEM_SOUND_EFFECTS} */
+ public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission"
+ + ".DISABLE_SYSTEM_SOUND_EFFECTS";
+ /** See {@code Manifest#REMOTE_DISPLAY_PROVIDER} */
+ public static final String REMOTE_DISPLAY_PROVIDER =
+ "android.permission.REMOTE_DISPLAY_PROVIDER";
+ /** See {@code Manifest#CAPTURE_SECURE_VIDEO_OUTPUT} */
+ public static final String CAPTURE_SECURE_VIDEO_OUTPUT =
+ "android.permission.CAPTURE_SECURE_VIDEO_OUTPUT";
+ /** See {@code Manifest#MEDIA_CONTENT_CONTROL} */
+ public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
+ /** See {@code Manifest#SET_VOLUME_KEY_LONG_PRESS_LISTENER} */
+ public static final String SET_VOLUME_KEY_LONG_PRESS_LISTENER =
+ "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER";
+ /** See {@code Manifest#SET_MEDIA_KEY_LISTENER} */
+ public static final String SET_MEDIA_KEY_LISTENER = "android.permission"
+ + ".SET_MEDIA_KEY_LISTENER";
+ /** See {@code Manifest#BRICK} */
+ public static final String BRICK = "android.permission.BRICK";
+ /** See {@code Manifest#REBOOT} */
+ public static final String REBOOT = "android.permission.REBOOT";
+ /** See {@code Manifest#DEVICE_POWER} */
+ public static final String DEVICE_POWER = "android.permission.DEVICE_POWER";
+ /** See {@code Manifest#POWER_SAVER} */
+ public static final String POWER_SAVER = "android.permission.POWER_SAVER";
+ /** See {@code Manifest#BATTERY_PREDICTION} */
+ public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION";
+ /** See {@code Manifest#USER_ACTIVITY} */
+ public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
+ /** See {@code Manifest#NET_TUNNELING} */
+ public static final String NET_TUNNELING = "android.permission.NET_TUNNELING";
+ /** See {@code Manifest#FACTORY_TEST} */
+ public static final String FACTORY_TEST = "android.permission.FACTORY_TEST";
+ /** See {@code Manifest#BROADCAST_CLOSE_SYSTEM_DIALOGS} */
+ public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS =
+ "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
+ /** See {@code Manifest#BROADCAST_PACKAGE_REMOVED} */
+ public static final String BROADCAST_PACKAGE_REMOVED =
+ "android.permission.BROADCAST_PACKAGE_REMOVED";
+ /** See {@code Manifest#BROADCAST_SMS} */
+ public static final String BROADCAST_SMS = "android.permission.BROADCAST_SMS";
+ /** See {@code Manifest#BROADCAST_WAP_PUSH} */
+ public static final String BROADCAST_WAP_PUSH = "android.permission.BROADCAST_WAP_PUSH";
+ /** See {@code Manifest#BROADCAST_NETWORK_PRIVILEGED} */
+ public static final String BROADCAST_NETWORK_PRIVILEGED = "android.permission"
+ + ".BROADCAST_NETWORK_PRIVILEGED";
+ /** See {@code Manifest#MASTER_CLEAR} */
+ public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
+ /** See {@code Manifest#CALL_PRIVILEGED} */
+ public static final String CALL_PRIVILEGED = "android.permission.CALL_PRIVILEGED";
+ /** See {@code Manifest#PERFORM_CDMA_PROVISIONING} */
+ public static final String PERFORM_CDMA_PROVISIONING =
+ "android.permission.PERFORM_CDMA_PROVISIONING";
+ /** See {@code Manifest#PERFORM_SIM_ACTIVATION} */
+ public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION";
+ /** See {@code Manifest#CONTROL_LOCATION_UPDATES} */
+ public static final String CONTROL_LOCATION_UPDATES =
+ "android.permission.CONTROL_LOCATION_UPDATES";
+ /** See {@code Manifest#ACCESS_CHECKIN_PROPERTIES} */
+ public static final String ACCESS_CHECKIN_PROPERTIES = "android.permission"
+ + ".ACCESS_CHECKIN_PROPERTIES";
+ /** See {@code Manifest#PACKAGE_USAGE_STATS} */
+ public static final String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS";
+ /** See {@code Manifest#LOADER_USAGE_STATS} */
+ public static final String LOADER_USAGE_STATS = "android.permission.LOADER_USAGE_STATS";
+ /** See {@code Manifest#OBSERVE_APP_USAGE} */
+ public static final String OBSERVE_APP_USAGE = "android.permission.OBSERVE_APP_USAGE";
+ /** See {@code Manifest#CHANGE_APP_IDLE_STATE} */
+ public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
+ /** See {@code Manifest#CHANGE_APP_LAUNCH_TIME_ESTIMATE} */
+ public static final String CHANGE_APP_LAUNCH_TIME_ESTIMATE =
+ "android.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE";
+ /** See {@code Manifest#CHANGE_DEVICE_IDLE_TEMP_WHITELIST} */
+ public static final String CHANGE_DEVICE_IDLE_TEMP_WHITELIST =
+ "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST";
+ /** See {@code Manifest#REQUEST_IGNORE_BATTERY_OPTIMIZATIONS} */
+ public static final String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS =
+ "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
+ /** See {@code Manifest#BATTERY_STATS} */
+ public static final String BATTERY_STATS = "android.permission.BATTERY_STATS";
+ /** See {@code Manifest#STATSCOMPANION} */
+ public static final String STATSCOMPANION = "android.permission.STATSCOMPANION";
+ /** See {@code Manifest#REGISTER_STATS_PULL_ATOM} */
+ public static final String REGISTER_STATS_PULL_ATOM = "android.permission"
+ + ".REGISTER_STATS_PULL_ATOM";
+ /** See {@code Manifest#BACKUP} */
+ public static final String BACKUP = "android.permission.BACKUP";
+ /** See {@code Manifest#MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE} */
+ public static final String MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE = "android.permission"
+ + ".MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE";
+ /** See {@code Manifest#RECOVER_KEYSTORE} */
+ public static final String RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE";
+ /** See {@code Manifest#CONFIRM_FULL_BACKUP} */
+ public static final String CONFIRM_FULL_BACKUP = "android.permission.CONFIRM_FULL_BACKUP";
+ /** See {@code Manifest#BIND_REMOTEVIEWS} */
+ public static final String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
+ /** See {@code Manifest#BIND_APPWIDGET} */
+ public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
+ /** See {@code Manifest#MANAGE_SLICE_PERMISSIONS} */
+ public static final String MANAGE_SLICE_PERMISSIONS =
+ "android.permission.MANAGE_SLICE_PERMISSIONS";
+ /** See {@code Manifest#BIND_KEYGUARD_APPWIDGET} */
+ public static final String BIND_KEYGUARD_APPWIDGET = "android.permission"
+ + ".BIND_KEYGUARD_APPWIDGET";
+ /** See {@code Manifest#MODIFY_APPWIDGET_BIND_PERMISSIONS} */
+ public static final String MODIFY_APPWIDGET_BIND_PERMISSIONS =
+ "android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS";
+ /** See {@code Manifest#CHANGE_BACKGROUND_DATA_SETTING} */
+ public static final String CHANGE_BACKGROUND_DATA_SETTING =
+ "android.permission.CHANGE_BACKGROUND_DATA_SETTING";
+ /** See {@code Manifest#GLOBAL_SEARCH} */
+ public static final String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH";
+ /** See {@code Manifest#GLOBAL_SEARCH_CONTROL} */
+ public static final String GLOBAL_SEARCH_CONTROL = "android.permission.GLOBAL_SEARCH_CONTROL";
+ /** See {@code Manifest#READ_SEARCH_INDEXABLES} */
+ public static final String READ_SEARCH_INDEXABLES = "android.permission"
+ + ".READ_SEARCH_INDEXABLES";
+ /** See {@code Manifest#BIND_SETTINGS_SUGGESTIONS_SERVICE} */
+ public static final String BIND_SETTINGS_SUGGESTIONS_SERVICE = "android.permission"
+ + ".BIND_SETTINGS_SUGGESTIONS_SERVICE";
+ /** See {@code Manifest#WRITE_SETTINGS_HOMEPAGE_DATA} */
+ public static final String WRITE_SETTINGS_HOMEPAGE_DATA = "android.permission"
+ + ".WRITE_SETTINGS_HOMEPAGE_DATA";
+ /** See {@code Manifest#LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK} */
+ public static final String LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK = "android.permission"
+ + ".LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK";
+ /** See {@code Manifest#ALLOW_PLACE_IN_MULTI_PANE_SETTINGS} */
+ public static final String ALLOW_PLACE_IN_MULTI_PANE_SETTINGS =
+ "android.permission.ALLOW_PLACE_IN_MULTI_PANE_SETTINGS";
+ /** See {@code Manifest#SET_WALLPAPER_COMPONENT} */
+ public static final String SET_WALLPAPER_COMPONENT = "android.permission"
+ + ".SET_WALLPAPER_COMPONENT";
+ /** See {@code Manifest#READ_DREAM_STATE} */
+ public static final String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE";
+ /** See {@code Manifest#WRITE_DREAM_STATE} */
+ public static final String WRITE_DREAM_STATE = "android.permission.WRITE_DREAM_STATE";
+ /** See {@code Manifest#READ_DREAM_SUPPRESSION} */
+ public static final String READ_DREAM_SUPPRESSION = "android.permission"
+ + ".READ_DREAM_SUPPRESSION";
+ /** See {@code Manifest#ACCESS_CACHE_FILESYSTEM} */
+ public static final String ACCESS_CACHE_FILESYSTEM = "android.permission"
+ + ".ACCESS_CACHE_FILESYSTEM";
+ /** See {@code Manifest#COPY_PROTECTED_DATA} */
+ public static final String COPY_PROTECTED_DATA = "android.permission.COPY_PROTECTED_DATA";
+ /** See {@code Manifest#CRYPT_KEEPER} */
+ public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER";
+ /** See {@code Manifest#READ_NETWORK_USAGE_HISTORY} */
+ public static final String READ_NETWORK_USAGE_HISTORY =
+ "android.permission.READ_NETWORK_USAGE_HISTORY";
+ /** See {@code Manifest#MANAGE_NETWORK_POLICY} */
+ public static final String MANAGE_NETWORK_POLICY = "android.permission.MANAGE_NETWORK_POLICY";
+ /** See {@code Manifest#MODIFY_NETWORK_ACCOUNTING} */
+ public static final String MODIFY_NETWORK_ACCOUNTING =
+ "android.permission.MODIFY_NETWORK_ACCOUNTING";
+ /** See {@code Manifest#MANAGE_SUBSCRIPTION_PLANS} */
+ public static final String MANAGE_SUBSCRIPTION_PLANS = "android.permission"
+ + ".MANAGE_SUBSCRIPTION_PLANS";
+ /** See {@code Manifest#C2D_MESSAGE} */
+ public static final String C2D_MESSAGE = "android.intent.category.MASTER_CLEAR.permission"
+ + ".C2D_MESSAGE";
+ /** See {@code Manifest#PACKAGE_VERIFICATION_AGENT} */
+ public static final String PACKAGE_VERIFICATION_AGENT =
+ "android.permission.PACKAGE_VERIFICATION_AGENT";
+ /** See {@code Manifest#BIND_PACKAGE_VERIFIER} */
+ public static final String BIND_PACKAGE_VERIFIER = "android.permission.BIND_PACKAGE_VERIFIER";
+ /** See {@code Manifest#PACKAGE_ROLLBACK_AGENT} */
+ public static final String PACKAGE_ROLLBACK_AGENT = "android.permission.PACKAGE_ROLLBACK_AGENT";
+ /** See {@code Manifest#MANAGE_ROLLBACKS} */
+ public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
+ /** See {@code Manifest#TEST_MANAGE_ROLLBACKS} */
+ public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
+ /** See {@code Manifest#SET_HARMFUL_APP_WARNINGS} */
+ public static final String SET_HARMFUL_APP_WARNINGS =
+ "android.permission.SET_HARMFUL_APP_WARNINGS";
+ /** See {@code Manifest#INTENT_FILTER_VERIFICATION_AGENT} */
+ public static final String INTENT_FILTER_VERIFICATION_AGENT =
+ "android.permission.INTENT_FILTER_VERIFICATION_AGENT";
+ /** See {@code Manifest#BIND_INTENT_FILTER_VERIFIER} */
+ public static final String BIND_INTENT_FILTER_VERIFIER = "android.permission"
+ + ".BIND_INTENT_FILTER_VERIFIER";
+ /** See {@code Manifest#DOMAIN_VERIFICATION_AGENT} */
+ public static final String DOMAIN_VERIFICATION_AGENT = "android.permission"
+ + ".DOMAIN_VERIFICATION_AGENT";
+ /** See {@code Manifest#BIND_DOMAIN_VERIFICATION_AGENT} */
+ public static final String BIND_DOMAIN_VERIFICATION_AGENT =
+ "android.permission.BIND_DOMAIN_VERIFICATION_AGENT";
+ /** See {@code Manifest#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION} */
+ public static final String UPDATE_DOMAIN_VERIFICATION_USER_SELECTION =
+ "android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION";
+ /** See {@code Manifest#SERIAL_PORT} */
+ public static final String SERIAL_PORT = "android.permission.SERIAL_PORT";
+ /** See {@code Manifest#ACCESS_CONTENT_PROVIDERS_EXTERNALLY} */
+ public static final String ACCESS_CONTENT_PROVIDERS_EXTERNALLY = "android.permission"
+ + ".ACCESS_CONTENT_PROVIDERS_EXTERNALLY";
+ /** See {@code Manifest#UPDATE_LOCK} */
+ public static final String UPDATE_LOCK = "android.permission.UPDATE_LOCK";
+ /** See {@code Manifest#REQUEST_NOTIFICATION_ASSISTANT_SERVICE} */
+ public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission"
+ + ".REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
+ /** See {@code Manifest#ACCESS_NOTIFICATIONS} */
+ public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS";
+ /** See {@code Manifest#ACCESS_NOTIFICATION_POLICY} */
+ public static final String ACCESS_NOTIFICATION_POLICY =
+ "android.permission.ACCESS_NOTIFICATION_POLICY";
+ /** See {@code Manifest#MANAGE_NOTIFICATIONS} */
+ public static final String MANAGE_NOTIFICATIONS = "android.permission.MANAGE_NOTIFICATIONS";
+ /** See {@code Manifest#MANAGE_NOTIFICATION_LISTENERS} */
+ public static final String MANAGE_NOTIFICATION_LISTENERS =
+ "android.permission.MANAGE_NOTIFICATION_LISTENERS";
+ /** See {@code Manifest#USE_COLORIZED_NOTIFICATIONS} */
+ public static final String USE_COLORIZED_NOTIFICATIONS =
+ "android.permission.USE_COLORIZED_NOTIFICATIONS";
+ /** See {@code Manifest#ACCESS_KEYGUARD_SECURE_STORAGE} */
+ public static final String ACCESS_KEYGUARD_SECURE_STORAGE = "android.permission"
+ + ".ACCESS_KEYGUARD_SECURE_STORAGE";
+ /** See {@code Manifest#SET_INITIAL_LOCK} */
+ public static final String SET_INITIAL_LOCK = "android.permission.SET_INITIAL_LOCK";
+ /** See {@code Manifest#SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS} */
+ public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS =
+ "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS";
+ /** See {@code Manifest#MANAGE_FINGERPRINT} */
+ public static final String MANAGE_FINGERPRINT = "android.permission.MANAGE_FINGERPRINT";
+ /** See {@code Manifest#RESET_FINGERPRINT_LOCKOUT} */
+ public static final String RESET_FINGERPRINT_LOCKOUT = "android.permission"
+ + ".RESET_FINGERPRINT_LOCKOUT";
+ /** See {@code Manifest#TEST_BIOMETRIC} */
+ public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
+ /** See {@code Manifest#MANAGE_BIOMETRIC} */
+ public static final String MANAGE_BIOMETRIC = "android.permission.MANAGE_BIOMETRIC";
+ /** See {@code Manifest#USE_BIOMETRIC_INTERNAL} */
+ public static final String USE_BIOMETRIC_INTERNAL = "android.permission"
+ + ".USE_BIOMETRIC_INTERNAL";
+ /** See {@code Manifest#MANAGE_BIOMETRIC_DIALOG} */
+ public static final String MANAGE_BIOMETRIC_DIALOG =
+ "android.permission.MANAGE_BIOMETRIC_DIALOG";
+ /** See {@code Manifest#CONTROL_KEYGUARD} */
+ public static final String CONTROL_KEYGUARD = "android.permission.CONTROL_KEYGUARD";
+ /** See {@code Manifest#CONTROL_KEYGUARD_SECURE_NOTIFICATIONS} */
+ public static final String CONTROL_KEYGUARD_SECURE_NOTIFICATIONS = "android.permission"
+ + ".CONTROL_KEYGUARD_SECURE_NOTIFICATIONS";
+ /** See {@code Manifest#TRUST_LISTENER} */
+ public static final String TRUST_LISTENER = "android.permission.TRUST_LISTENER";
+ /** See {@code Manifest#PROVIDE_TRUST_AGENT} */
+ public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
+ /** See {@code Manifest#SHOW_KEYGUARD_MESSAGE} */
+ public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE";
+ /** See {@code Manifest#LAUNCH_TRUST_AGENT_SETTINGS} */
+ public static final String LAUNCH_TRUST_AGENT_SETTINGS = "android.permission"
+ + ".LAUNCH_TRUST_AGENT_SETTINGS";
+ /** See {@code Manifest#BIND_TRUST_AGENT} */
+ public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
+ /** See {@code Manifest#BIND_NOTIFICATION_LISTENER_SERVICE} */
+ public static final String BIND_NOTIFICATION_LISTENER_SERVICE =
+ "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
+ /** See {@code Manifest#BIND_NOTIFICATION_ASSISTANT_SERVICE} */
+ public static final String BIND_NOTIFICATION_ASSISTANT_SERVICE =
+ "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
+ /** See {@code Manifest#BIND_CHOOSER_TARGET_SERVICE} */
+ public static final String BIND_CHOOSER_TARGET_SERVICE =
+ "android.permission.BIND_CHOOSER_TARGET_SERVICE";
+ /** See {@code Manifest#PROVIDE_RESOLVER_RANKER_SERVICE} */
+ public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission"
+ + ".PROVIDE_RESOLVER_RANKER_SERVICE";
+ /** See {@code Manifest#BIND_RESOLVER_RANKER_SERVICE} */
+ public static final String BIND_RESOLVER_RANKER_SERVICE =
+ "android.permission.BIND_RESOLVER_RANKER_SERVICE";
+ /** See {@code Manifest#BIND_CONDITION_PROVIDER_SERVICE} */
+ public static final String BIND_CONDITION_PROVIDER_SERVICE =
+ "android.permission.BIND_CONDITION_PROVIDER_SERVICE";
+ /** See {@code Manifest#BIND_DREAM_SERVICE} */
+ public static final String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE";
+ /** See {@code Manifest#BIND_CACHE_QUOTA_SERVICE} */
+ public static final String BIND_CACHE_QUOTA_SERVICE = "android.permission"
+ + ".BIND_CACHE_QUOTA_SERVICE";
+ /** See {@code Manifest#INVOKE_CARRIER_SETUP} */
+ public static final String INVOKE_CARRIER_SETUP = "android.permission.INVOKE_CARRIER_SETUP";
+ /** See {@code Manifest#ACCESS_NETWORK_CONDITIONS} */
+ public static final String ACCESS_NETWORK_CONDITIONS =
+ "android.permission.ACCESS_NETWORK_CONDITIONS";
+ /** See {@code Manifest#ACCESS_DRM_CERTIFICATES} */
+ public static final String ACCESS_DRM_CERTIFICATES =
+ "android.permission.ACCESS_DRM_CERTIFICATES";
+ /** See {@code Manifest#MANAGE_MEDIA_PROJECTION} */
+ public static final String MANAGE_MEDIA_PROJECTION =
+ "android.permission.MANAGE_MEDIA_PROJECTION";
+ /** See {@code Manifest#READ_INSTALL_SESSIONS} */
+ public static final String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS";
+ /** See {@code Manifest#REMOVE_DRM_CERTIFICATES} */
+ public static final String REMOVE_DRM_CERTIFICATES = "android.permission"
+ + ".REMOVE_DRM_CERTIFICATES";
+ /** See {@code Manifest#BIND_CARRIER_MESSAGING_SERVICE} */
+ public static final String BIND_CARRIER_MESSAGING_SERVICE =
+ "android.permission.BIND_CARRIER_MESSAGING_SERVICE";
+ /** See {@code Manifest#ACCESS_VOICE_INTERACTION_SERVICE} */
+ public static final String ACCESS_VOICE_INTERACTION_SERVICE =
+ "android.permission.ACCESS_VOICE_INTERACTION_SERVICE";
+ /** See {@code Manifest#BIND_CARRIER_SERVICES} */
+ public static final String BIND_CARRIER_SERVICES = "android.permission.BIND_CARRIER_SERVICES";
+ /** See {@code Manifest#START_VIEW_PERMISSION_USAGE} */
+ public static final String START_VIEW_PERMISSION_USAGE = "android.permission"
+ + ".START_VIEW_PERMISSION_USAGE";
+ /** See {@code Manifest#QUERY_DO_NOT_ASK_CREDENTIALS_ON_BOOT} */
+ public static final String QUERY_DO_NOT_ASK_CREDENTIALS_ON_BOOT = "android.permission"
+ + ".QUERY_DO_NOT_ASK_CREDENTIALS_ON_BOOT";
+ /** See {@code Manifest#KILL_UID} */
+ public static final String KILL_UID = "android.permission.KILL_UID";
+ /** See {@code Manifest#LOCAL_MAC_ADDRESS} */
+ public static final String LOCAL_MAC_ADDRESS = "android.permission.LOCAL_MAC_ADDRESS";
+ /** See {@code Manifest#PEERS_MAC_ADDRESS} */
+ public static final String PEERS_MAC_ADDRESS = "android.permission.PEERS_MAC_ADDRESS";
+ /** See {@code Manifest#DISPATCH_NFC_MESSAGE} */
+ public static final String DISPATCH_NFC_MESSAGE = "android.permission.DISPATCH_NFC_MESSAGE";
+ /** See {@code Manifest#MODIFY_DAY_NIGHT_MODE} */
+ public static final String MODIFY_DAY_NIGHT_MODE = "android.permission.MODIFY_DAY_NIGHT_MODE";
+ /** See {@code Manifest#ENTER_CAR_MODE_PRIORITIZED} */
+ public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission"
+ + ".ENTER_CAR_MODE_PRIORITIZED";
+ /** See {@code Manifest#HANDLE_CAR_MODE_CHANGES} */
+ public static final String HANDLE_CAR_MODE_CHANGES = "android.permission"
+ + ".HANDLE_CAR_MODE_CHANGES";
+ /** See {@code Manifest#SEND_CATEGORY_CAR_NOTIFICATIONS} */
+ public static final String SEND_CATEGORY_CAR_NOTIFICATIONS =
+ "android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS";
+ /** See {@code Manifest#ACCESS_INSTANT_APPS} */
+ public static final String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS";
+ /** See {@code Manifest#VIEW_INSTANT_APPS} */
+ public static final String VIEW_INSTANT_APPS = "android.permission.VIEW_INSTANT_APPS";
+ /** See {@code Manifest#WRITE_COMMUNAL_STATE} */
+ public static final String WRITE_COMMUNAL_STATE = "android.permission.WRITE_COMMUNAL_STATE";
+ /** See {@code Manifest#READ_COMMUNAL_STATE} */
+ public static final String READ_COMMUNAL_STATE = "android.permission.READ_COMMUNAL_STATE";
+ /** See {@code Manifest#MANAGE_BIND_INSTANT_SERVICE} */
+ public static final String MANAGE_BIND_INSTANT_SERVICE =
+ "android.permission.MANAGE_BIND_INSTANT_SERVICE";
+ /** See {@code Manifest#RECEIVE_MEDIA_RESOURCE_USAGE} */
+ public static final String RECEIVE_MEDIA_RESOURCE_USAGE = "android.permission.RECEIVE_MEDIA_RESOURCE_USAGE";
+ /** See {@code Manifest#MANAGE_SOUND_TRIGGER} */
+ public static final String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER";
+ /** See {@code Manifest#SOUND_TRIGGER_RUN_IN_BATTERY_SAVER} */
+ public static final String SOUND_TRIGGER_RUN_IN_BATTERY_SAVER = "android.permission"
+ + ".SOUND_TRIGGER_RUN_IN_BATTERY_SAVER";
+ /** See {@code Manifest#BIND_SOUND_TRIGGER_DETECTION_SERVICE} */
+ public static final String BIND_SOUND_TRIGGER_DETECTION_SERVICE = "android.permission"
+ + ".BIND_SOUND_TRIGGER_DETECTION_SERVICE";
+ /** See {@code Manifest#DISPATCH_PROVISIONING_MESSAGE} */
+ public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission"
+ + ".DISPATCH_PROVISIONING_MESSAGE";
+ /** See {@code Manifest#READ_BLOCKED_NUMBERS} */
+ public static final String READ_BLOCKED_NUMBERS = "android.permission.READ_BLOCKED_NUMBERS";
+ /** See {@code Manifest#WRITE_BLOCKED_NUMBERS} */
+ public static final String WRITE_BLOCKED_NUMBERS = "android.permission.WRITE_BLOCKED_NUMBERS";
+ /** See {@code Manifest#BIND_VR_LISTENER_SERVICE} */
+ public static final String BIND_VR_LISTENER_SERVICE = "android.permission"
+ + ".BIND_VR_LISTENER_SERVICE";
+ /** See {@code Manifest#RESTRICTED_VR_ACCESS} */
+ public static final String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS";
+ /** See {@code Manifest#ACCESS_VR_MANAGER} */
+ public static final String ACCESS_VR_MANAGER = "android.permission.ACCESS_VR_MANAGER";
+ /** See {@code Manifest#ACCESS_VR_STATE} */
+ public static final String ACCESS_VR_STATE = "android.permission.ACCESS_VR_STATE";
+ /** See {@code Manifest#UPDATE_LOCK_TASK_PACKAGES} */
+ public static final String UPDATE_LOCK_TASK_PACKAGES =
+ "android.permission.UPDATE_LOCK_TASK_PACKAGES";
+ /** See {@code Manifest#SUBSTITUTE_NOTIFICATION_APP_NAME} */
+ public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission"
+ + ".SUBSTITUTE_NOTIFICATION_APP_NAME";
+ /** See {@code Manifest#NOTIFICATION_DURING_SETUP} */
+ public static final String NOTIFICATION_DURING_SETUP =
+ "android.permission.NOTIFICATION_DURING_SETUP";
+ /** See {@code Manifest#MANAGE_AUTO_FILL} */
+ public static final String MANAGE_AUTO_FILL = "android.permission.MANAGE_AUTO_FILL";
+ /** See {@code Manifest#MANAGE_CONTENT_CAPTURE} */
+ public static final String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE";
+ /** See {@code Manifest#MANAGE_ROTATION_RESOLVER} */
+ public static final String MANAGE_ROTATION_RESOLVER = "android.permission"
+ + ".MANAGE_ROTATION_RESOLVER";
+ /** See {@code Manifest#MANAGE_MUSIC_RECOGNITION} */
+ public static final String MANAGE_MUSIC_RECOGNITION =
+ "android.permission.MANAGE_MUSIC_RECOGNITION";
+ /** See {@code Manifest#MANAGE_SPEECH_RECOGNITION} */
+ public static final String MANAGE_SPEECH_RECOGNITION =
+ "android.permission.MANAGE_SPEECH_RECOGNITION";
+ /** See {@code Manifest#MANAGE_CONTENT_SUGGESTIONS} */
+ public static final String MANAGE_CONTENT_SUGGESTIONS = "android.permission"
+ + ".MANAGE_CONTENT_SUGGESTIONS";
+ /** See {@code Manifest#MANAGE_APP_PREDICTIONS} */
+ public static final String MANAGE_APP_PREDICTIONS = "android.permission.MANAGE_APP_PREDICTIONS";
+ /** See {@code Manifest#MANAGE_SEARCH_UI} */
+ public static final String MANAGE_SEARCH_UI = "android.permission.MANAGE_SEARCH_UI";
+ /** See {@code Manifest#MANAGE_SMARTSPACE} */
+ public static final String MANAGE_SMARTSPACE = "android.permission.MANAGE_SMARTSPACE";
+ /** See {@code Manifest#MODIFY_THEME_OVERLAY} */
+ public static final String MODIFY_THEME_OVERLAY = "android.permission.MODIFY_THEME_OVERLAY";
+ /** See {@code Manifest#INSTANT_APP_FOREGROUND_SERVICE} */
+ public static final String INSTANT_APP_FOREGROUND_SERVICE =
+ "android.permission.INSTANT_APP_FOREGROUND_SERVICE";
+ /** See {@code Manifest#FOREGROUND_SERVICE} */
+ public static final String FOREGROUND_SERVICE = "android.permission.FOREGROUND_SERVICE";
+ /** See {@code Manifest#ACCESS_SHORTCUTS} */
+ public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
+ /** See {@code Manifest#UNLIMITED_SHORTCUTS_API_CALLS} */
+ public static final String UNLIMITED_SHORTCUTS_API_CALLS =
+ "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
+ /** See {@code Manifest#READ_RUNTIME_PROFILES} */
+ public static final String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES";
+ /** See {@code Manifest#MANAGE_AUDIO_POLICY} */
+ public static final String MANAGE_AUDIO_POLICY = "android.permission.MANAGE_AUDIO_POLICY";
+ /** See {@code Manifest#MODIFY_QUIET_MODE} */
+ public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE";
+ /** See {@code Manifest#MANAGE_CAMERA} */
+ public static final String MANAGE_CAMERA = "android.permission.MANAGE_CAMERA";
+ /** See {@code Manifest#CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS} */
+ public static final String CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS =
+ "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
+ /** See {@code Manifest#WATCH_APPOPS} */
+ public static final String WATCH_APPOPS = "android.permission.WATCH_APPOPS";
+ /** See {@code Manifest#DISABLE_HIDDEN_API_CHECKS} */
+ public static final String DISABLE_HIDDEN_API_CHECKS =
+ "android.permission.DISABLE_HIDDEN_API_CHECKS";
+ /** See {@code Manifest#MONITOR_DEFAULT_SMS_PACKAGE} */
+ public static final String MONITOR_DEFAULT_SMS_PACKAGE =
+ "android.permission.MONITOR_DEFAULT_SMS_PACKAGE";
+ /** See {@code Manifest#BIND_CARRIER_MESSAGING_CLIENT_SERVICE} */
+ public static final String BIND_CARRIER_MESSAGING_CLIENT_SERVICE =
+ "android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE";
+ /** See {@code Manifest#BIND_EXPLICIT_HEALTH_CHECK_SERVICE} */
+ public static final String BIND_EXPLICIT_HEALTH_CHECK_SERVICE =
+ "android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE";
+ /** See {@code Manifest#BIND_EXTERNAL_STORAGE_SERVICE} */
+ public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission"
+ + ".BIND_EXTERNAL_STORAGE_SERVICE";
+ /** See {@code Manifest#MANAGE_APPOPS} */
+ public static final String MANAGE_APPOPS = "android.permission.MANAGE_APPOPS";
+ /** See {@code Manifest#READ_CLIPBOARD_IN_BACKGROUND} */
+ public static final String READ_CLIPBOARD_IN_BACKGROUND = "android.permission"
+ + ".READ_CLIPBOARD_IN_BACKGROUND";
+ /** See {@code Manifest#MANAGE_ACCESSIBILITY} */
+ public static final String MANAGE_ACCESSIBILITY = "android.permission.MANAGE_ACCESSIBILITY";
+ /** See {@code Manifest#GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS} */
+ public static final String GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS =
+ "android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS";
+ /** See {@code Manifest#MARK_DEVICE_ORGANIZATION_OWNED} */
+ public static final String MARK_DEVICE_ORGANIZATION_OWNED = "android.permission"
+ + ".MARK_DEVICE_ORGANIZATION_OWNED";
+ /** See {@code Manifest#SMS_FINANCIAL_TRANSACTIONS} */
+ public static final String SMS_FINANCIAL_TRANSACTIONS =
+ "android.permission.SMS_FINANCIAL_TRANSACTIONS";
+ /** See {@code Manifest#USE_FULL_SCREEN_INTENT} */
+ public static final String USE_FULL_SCREEN_INTENT = "android.permission.USE_FULL_SCREEN_INTENT";
+ /** See {@code Manifest#SEND_DEVICE_CUSTOMIZATION_READY} */
+ public static final String SEND_DEVICE_CUSTOMIZATION_READY =
+ "android.permission.SEND_DEVICE_CUSTOMIZATION_READY";
+ /** See {@code Manifest#RECEIVE_DEVICE_CUSTOMIZATION_READY} */
+ public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY =
+ "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY";
+ /** See {@code Manifest#AMBIENT_WALLPAPER} */
+ public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
+ /** See {@code Manifest#MANAGE_SENSOR_PRIVACY} */
+ public static final String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY";
+ /** See {@code Manifest#OBSERVE_SENSOR_PRIVACY} */
+ public static final String OBSERVE_SENSOR_PRIVACY = "android.permission.OBSERVE_SENSOR_PRIVACY";
+ /** See {@code Manifest#REVIEW_ACCESSIBILITY_SERVICES} */
+ public static final String REVIEW_ACCESSIBILITY_SERVICES =
+ "android.permission.REVIEW_ACCESSIBILITY_SERVICES";
+ /** See {@code Manifest#SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON} */
+ public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON =
+ "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON";
+ /** See {@code Manifest#ACCESS_SHARED_LIBRARIES} */
+ public static final String ACCESS_SHARED_LIBRARIES =
+ "android.permission.ACCESS_SHARED_LIBRARIES";
+ /** See {@code Manifest#LOG_COMPAT_CHANGE} */
+ public static final String LOG_COMPAT_CHANGE = "android.permission.LOG_COMPAT_CHANGE";
+ /** See {@code Manifest#READ_COMPAT_CHANGE_CONFIG} */
+ public static final String READ_COMPAT_CHANGE_CONFIG =
+ "android.permission.READ_COMPAT_CHANGE_CONFIG";
+ /** See {@code Manifest#OVERRIDE_COMPAT_CHANGE_CONFIG} */
+ public static final String OVERRIDE_COMPAT_CHANGE_CONFIG =
+ "android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG";
+ /** See {@code Manifest#OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD} */
+ public static final String OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD =
+ "android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD";
+ /** See {@code Manifest#MONITOR_INPUT} */
+ public static final String MONITOR_INPUT = "android.permission.MONITOR_INPUT";
+ /** See {@code Manifest#ASSOCIATE_INPUT_DEVICE_TO_DISPLAY} */
+ public static final String ASSOCIATE_INPUT_DEVICE_TO_DISPLAY =
+ "android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY";
+ /** See {@code Manifest#QUERY_ALL_PACKAGES} */
+ public static final String QUERY_ALL_PACKAGES = "android.permission.QUERY_ALL_PACKAGES";
+ /** See {@code Manifest#PEEK_DROPBOX_DATA} */
+ public static final String PEEK_DROPBOX_DATA = "android.permission.PEEK_DROPBOX_DATA";
+ /** See {@code Manifest#ACCESS_TV_TUNER} */
+ public static final String ACCESS_TV_TUNER = "android.permission.ACCESS_TV_TUNER";
+ /** See {@code Manifest#ACCESS_TV_DESCRAMBLER} */
+ public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER";
+ /** See {@code Manifest#ACCESS_TV_SHARED_FILTER} */
+ public static final String ACCESS_TV_SHARED_FILTER =
+ "android.permission.ACCESS_TV_SHARED_FILTER";
+ /** See {@code Manifest#ADD_TRUSTED_DISPLAY} */
+ public static final String ADD_TRUSTED_DISPLAY = "android.permission.ADD_TRUSTED_DISPLAY";
+ /** See {@code Manifest#ADD_ALWAYS_UNLOCKED_DISPLAY} */
+ public static final String ADD_ALWAYS_UNLOCKED_DISPLAY =
+ "android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY";
+ /** See {@code Manifest#ACCESS_LOCUS_ID_USAGE_STATS} */
+ public static final String ACCESS_LOCUS_ID_USAGE_STATS =
+ "android.permission.ACCESS_LOCUS_ID_USAGE_STATS";
+ /** See {@code Manifest#MANAGE_APP_HIBERNATION} */
+ public static final String MANAGE_APP_HIBERNATION = "android.permission.MANAGE_APP_HIBERNATION";
+ /** See {@code Manifest#RESET_APP_ERRORS} */
+ public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS";
+ /** See {@code Manifest#INPUT_CONSUMER} */
+ public static final String INPUT_CONSUMER = "android.permission.INPUT_CONSUMER";
+ /** See {@code Manifest#CONTROL_DEVICE_STATE} */
+ public static final String CONTROL_DEVICE_STATE = "android.permission.CONTROL_DEVICE_STATE";
+ /** See {@code Manifest#BIND_DISPLAY_HASHING_SERVICE} */
+ public static final String BIND_DISPLAY_HASHING_SERVICE =
+ "android.permission.BIND_DISPLAY_HASHING_SERVICE";
+ /** See {@code Manifest#MANAGE_TOAST_RATE_LIMITING} */
+ public static final String MANAGE_TOAST_RATE_LIMITING =
+ "android.permission.MANAGE_TOAST_RATE_LIMITING";
+ /** See {@code Manifest#MANAGE_GAME_MODE} */
+ public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE";
+ /** See {@code Manifest#SIGNAL_REBOOT_READINESS} */
+ public static final String SIGNAL_REBOOT_READINESS =
+ "android.permission.SIGNAL_REBOOT_READINESS";
+ /** See {@code Manifest#GET_PEOPLE_TILE_PREVIEW} */
+ public static final String GET_PEOPLE_TILE_PREVIEW =
+ "android.permission.GET_PEOPLE_TILE_PREVIEW";
+ /** See {@code Manifest#READ_PEOPLE_DATA} */
+ public static final String READ_PEOPLE_DATA = "android.permission.READ_PEOPLE_DATA";
+ /** See {@code Manifest#RENOUNCE_PERMISSIONS} */
+ public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS";
+ /** See {@code Manifest#READ_NEARBY_STREAMING_POLICY} */
+ public static final String READ_NEARBY_STREAMING_POLICY =
+ "android.permission.READ_NEARBY_STREAMING_POLICY";
+ /** See {@code Manifest#SET_CLIP_SOURCE} */
+ public static final String SET_CLIP_SOURCE = "android.permission.SET_CLIP_SOURCE";
+ /** See {@code Manifest#ACCESS_TUNED_INFO} */
+ public static final String ACCESS_TUNED_INFO = "android.permission.ACCESS_TUNED_INFO";
+ /** See {@code Manifest#UPDATE_PACKAGES_WITHOUT_USER_ACTION} */
+ public static final String UPDATE_PACKAGES_WITHOUT_USER_ACTION =
+ "android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION";
+ /** See {@code Manifest#CAPTURE_BLACKOUT_CONTENT} */
+ public static final String CAPTURE_BLACKOUT_CONTENT =
+ "android.permission.CAPTURE_BLACKOUT_CONTENT";
+ /** See {@code Manifest#READ_GLOBAL_APP_SEARCH_DATA} */
+ public static final String READ_GLOBAL_APP_SEARCH_DATA =
+ "android.permission.READ_GLOBAL_APP_SEARCH_DATA";
+ /** See {@code Manifest#CREATE_VIRTUAL_DEVICE} */
+ public static final String CREATE_VIRTUAL_DEVICE = "android.permission.CREATE_VIRTUAL_DEVICE";
+ /** See {@code Manifest#SEND_SAFETY_CENTER_UPDATE} */
+ public static final String SEND_SAFETY_CENTER_UPDATE =
+ "android.permission.SEND_SAFETY_CENTER_UPDATE";
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/Tags.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/utils/Tags.java
similarity index 100%
rename from common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/Tags.java
rename to common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/utils/Tags.java
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java
index ecc04e3..a9721a7 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java
@@ -19,6 +19,7 @@
import com.android.bedstead.nene.activities.Activities;
import com.android.bedstead.nene.annotations.Experimental;
import com.android.bedstead.nene.context.Context;
+import com.android.bedstead.nene.device.Device;
import com.android.bedstead.nene.devicepolicy.DevicePolicy;
import com.android.bedstead.nene.notifications.Notifications;
import com.android.bedstead.nene.packages.Packages;
@@ -77,6 +78,11 @@
return Notifications.sInstance;
}
+ /** Access Test APIs related to the device. */
+ public static Device device() {
+ return Device.sInstance;
+ }
+
/** @deprecated Use statically */
@Deprecated()
public TestApis() {
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/appops/AppOps.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/appops/AppOps.java
index ee9927f..d7ca18a 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/appops/AppOps.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/appops/AppOps.java
@@ -18,7 +18,7 @@
import static android.os.Build.VERSION_CODES.Q;
-import static com.android.bedstead.nene.permissions.Permissions.MANAGE_APP_OPS_MODES;
+import static com.android.bedstead.nene.permissions.CommonPermissions.MANAGE_APP_OPS_MODES;
import android.annotation.TargetApi;
import android.app.AppOpsManager;
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/device/Device.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/device/Device.java
new file mode 100644
index 0000000..b2530e8
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/device/Device.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.nene.device;
+
+import android.os.RemoteException;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.exceptions.AdbException;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.nene.utils.Poll;
+import com.android.bedstead.nene.utils.ShellCommand;
+
+/** Helper methods related to the device. */
+public final class Device {
+ public static final Device sInstance = new Device();
+ private static final UiDevice sDevice =
+ UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+ private Device() {
+
+ }
+
+ /**
+ * Turn the screen on.
+ */
+ public void wakeUp() {
+ try {
+ ShellCommand.builder("input keyevent")
+ .addOperand("KEYCODE_WAKEUP")
+ .validate(String::isEmpty)
+ .execute();
+ } catch (AdbException e) {
+ throw new NeneException("Error waking up device", e);
+ }
+
+ Poll.forValue("isScreenOn", this::isScreenOn)
+ .toBeEqualTo(true)
+ .errorOnFail()
+ .await();
+ }
+
+ /**
+ * True if the screen is on.
+ */
+ public boolean isScreenOn() {
+ try {
+ return sDevice.isScreenOn();
+ } catch (RemoteException e) {
+ throw new NeneException("Error getting isScreenOn", e);
+ }
+ }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DeviceOwner.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DeviceOwner.java
index ae3d100..908c419 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DeviceOwner.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DeviceOwner.java
@@ -16,10 +16,17 @@
package com.android.bedstead.nene.devicepolicy;
-import static com.android.bedstead.nene.permissions.Permissions.MANAGE_PROFILE_AND_DEVICE_OWNERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static com.android.bedstead.nene.permissions.Permissions.MANAGE_PROFILE_AND_DEVICE_OWNERS;
+import static com.android.compatibility.common.util.enterprise.DeviceAdminReceiverUtils.ACTION_DISABLE_SELF;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
+import android.content.Intent;
import android.os.Build;
import com.android.bedstead.nene.TestApis;
@@ -28,16 +35,24 @@
import com.android.bedstead.nene.packages.Package;
import com.android.bedstead.nene.permissions.PermissionContext;
import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.utils.Poll;
+import com.android.bedstead.nene.utils.Retry;
import com.android.bedstead.nene.utils.ShellCommand;
import com.android.bedstead.nene.utils.ShellCommandUtils;
import com.android.bedstead.nene.utils.Versions;
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+import java.time.Duration;
import java.util.Objects;
/**
* A reference to a Device Owner.
*/
public final class DeviceOwner extends DevicePolicyController {
+
+ private static final String TEST_APP_APP_COMPONENT_FACTORY =
+ "com.android.bedstead.testapp.TestAppAppComponentFactory";
+
DeviceOwner(UserReference user,
Package pkg,
ComponentName componentName) {
@@ -57,7 +72,8 @@
@Override
public void remove() {
- if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) {
+ if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)
+ || TestApis.packages().instrumented().isInstantApp()) {
removePreS();
return;
}
@@ -69,7 +85,18 @@
try (PermissionContext p =
TestApis.permissions().withPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)) {
devicePolicyManager.forceRemoveActiveAdmin(mComponentName, mUser.id());
+ } catch (SecurityException e) {
+ if (e.getMessage().contains("Attempt to remove non-test admin")
+ && TEST_APP_APP_COMPONENT_FACTORY.equals(mPackage.appComponentFactory())) {
+ removeTestApp();
+ } else {
+ throw e;
+ }
}
+
+ Poll.forValue("Device Owner", () -> TestApis.devicePolicy().getDeviceOwner())
+ .toBeNull()
+ .errorOnFail().await();
}
private void removePreS() {
@@ -79,7 +106,39 @@
.validate(ShellCommandUtils::startsWithSuccess)
.execute();
} catch (AdbException e) {
- throw new NeneException("Error removing device owner " + this, e);
+ if (mPackage.appComponentFactory().equals(TEST_APP_APP_COMPONENT_FACTORY)
+ && user().parent() == null) {
+ // We can't see why it failed so we'll try the test app version
+ removeTestApp();
+ } else {
+ throw new NeneException("Error removing device owner " + this, e);
+ }
+ }
+ }
+
+ private void removeTestApp() {
+ // Special case for removing TestApp DPCs - this works even when not testOnly
+ Intent intent = new Intent(ACTION_DISABLE_SELF);
+ intent.setComponent(new ComponentName(pkg().packageName(),
+ "com.android.bedstead.testapp.TestAppBroadcastController"));
+
+ try (PermissionContext p =
+ TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+ // If the profile isn't ready then the broadcast won't be sent and the profile owner
+ // will not be removed. So we can retry until the broadcast has been dealt with.
+ Retry.logic(() -> {
+ BlockingBroadcastReceiver b = new BlockingBroadcastReceiver(
+ TestApis.context().instrumentedContext());
+
+ TestApis.context().androidContextAsUser(mUser).sendOrderedBroadcast(
+ intent, /* receiverPermission= */ null, b, /* scheduler= */
+ null, /* initialCode= */
+ Activity.RESULT_CANCELED, /* initialData= */ null, /* initialExtras= */
+ null);
+
+ b.awaitForBroadcastOrFail(Duration.ofSeconds(30).toMillis());
+ assertThat(b.getResultCode()).isEqualTo(Activity.RESULT_OK);
+ }).timeout(Duration.ofMinutes(5)).runAndWrapException();
}
}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DevicePolicy.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DevicePolicy.java
index 2d8d21d..7a1eaf9 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DevicePolicy.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DevicePolicy.java
@@ -42,11 +42,14 @@
import com.android.bedstead.nene.packages.Package;
import com.android.bedstead.nene.permissions.PermissionContext;
import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.utils.Poll;
+import com.android.bedstead.nene.utils.Retry;
import com.android.bedstead.nene.utils.ShellCommand;
import com.android.bedstead.nene.utils.ShellCommandUtils;
import com.android.bedstead.nene.utils.Versions;
import com.android.compatibility.common.util.PollingCheck;
+import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
@@ -64,8 +67,6 @@
private static final String LOG_TAG = "DevicePolicy";
- private static final String USER_SETUP_COMPLETE_KEY = "user_setup_complete";
-
private final AdbDevicePolicyParser mParser;
private DeviceOwner mCachedDeviceOwner;
@@ -91,44 +92,40 @@
// TODO(b/187925230): If it fails, we check for terminal failure states - and if not
// we retry because if the profile owner was recently removed, it can take some time
// to be allowed to set it again
- retryIfNotTerminal(
- () -> command.executeOrThrowNeneException("Could not set profile owner for user "
- + user + " component " + profileOwnerComponent),
- () -> checkForTerminalProfileOwnerFailures(user, profileOwnerComponent),
- NeneException.class);
+ try {
+ Retry.logic(command::execute)
+ .terminalException((ex) -> {
+ if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) {
+ return false; // Just retry on old versions as we don't have stderr
+ }
+ if (ex instanceof AdbException) {
+ if (((AdbException) ex).error().contains("is being removed")) {
+ return false;
+ }
+ }
+
+ // Assume all other errors are terminal
+ return true;
+ })
+ .timeout(Duration.ofMinutes(5))
+ .run();
+ } catch (Throwable e) {
+ throw new NeneException("Could not set profile owner for user "
+ + user + " component " + profileOwnerComponent, e);
+ }
+
+ Poll.forValue("Profile Owner", () -> TestApis.devicePolicy().getProfileOwner(user))
+ .toNotBeNull()
+ .errorOnFail()
+ .await();
+
return new ProfileOwner(user,
TestApis.packages().find(
profileOwnerComponent.getPackageName()), profileOwnerComponent);
}
- private void checkForTerminalProfileOwnerFailures(
- UserReference user, ComponentName profileOwnerComponent) {
- ProfileOwner profileOwner = getProfileOwner(user);
- if (profileOwner != null) {
- // TODO(scottjonathan): Should we actually fail here if the component name is the
- // same?
-
- throw new NeneException(
- "Could not set profile owner for user " + user
- + " as a profile owner is already set: " + profileOwner);
- }
-
- Package pkg = TestApis.packages().find(
- profileOwnerComponent.getPackageName());
- if (!TestApis.packages().installedForUser(user).contains(pkg)) {
- throw new NeneException(
- "Could not set profile owner for user " + user
- + " as the package " + pkg + " is not installed");
- }
-
- if (!componentCanBeSetAsDeviceAdmin(profileOwnerComponent, user)) {
- throw new NeneException("Could not set profile owner for user "
- + user + " as component " + profileOwnerComponent + " is not valid");
- }
- }
-
/**
- * Get the profile owner for the instrumented user..
+ * Get the profile owner for the instrumented user.
*/
public ProfileOwner getProfileOwner() {
return getProfileOwner(TestApis.users().instrumented());
@@ -162,25 +159,27 @@
.getSystemService(DevicePolicyManager.class);
UserReference user = TestApis.users().system();
- boolean dpmUserSetupComplete = getUserSetupComplete(user);
+ boolean dpmUserSetupComplete = user.getSetupComplete();
Boolean currentUserSetupComplete = null;
try {
- setUserSetupComplete(user, false);
+ user.setSetupComplete(false);
try (PermissionContext p =
TestApis.permissions().withPermission(
MANAGE_PROFILE_AND_DEVICE_OWNERS, MANAGE_DEVICE_ADMINS,
INTERACT_ACROSS_USERS_FULL, INTERACT_ACROSS_USERS, CREATE_USERS)) {
- devicePolicyManager.setActiveAdmin(deviceOwnerComponent,
- /* refreshing= */ true, user.id());
// TODO(b/187925230): If it fails, we check for terminal failure states - and if not
// we retry because if the DO/PO was recently removed, it can take some time
// to be allowed to set it again
retryIfNotTerminal(
- () -> setDeviceOwnerOnly(devicePolicyManager,
- deviceOwnerComponent, "Nene", user.id()),
+ () -> {
+ devicePolicyManager.setActiveAdmin(deviceOwnerComponent,
+ /* refreshing= */ true, user.id());
+ setDeviceOwnerOnly(devicePolicyManager,
+ deviceOwnerComponent, "Nene", user.id());
+ },
() -> checkForTerminalDeviceOwnerFailures(
user, deviceOwnerComponent, /* allowAdditionalUsers= */ true),
NeneException.class);
@@ -188,15 +187,20 @@
throw new NeneException("Error setting device owner", e);
}
} finally {
- setUserSetupComplete(user, dpmUserSetupComplete);
+ user.setSetupComplete(dpmUserSetupComplete);
if (currentUserSetupComplete != null) {
- setUserSetupComplete(TestApis.users().current(), currentUserSetupComplete);
+ TestApis.users().current().setSetupComplete(currentUserSetupComplete);
}
}
Package deviceOwnerPackage = TestApis.packages().find(
deviceOwnerComponent.getPackageName());
+ Poll.forValue("Device Owner", () -> TestApis.devicePolicy().getDeviceOwner())
+ .toNotBeNull()
+ .errorOnFail()
+ .await();
+
return new DeviceOwner(user, deviceOwnerPackage, deviceOwnerComponent);
}
@@ -268,26 +272,6 @@
}
}
-
- private void setUserSetupComplete(UserReference user, boolean complete) {
- DevicePolicyManager devicePolicyManager =
- TestApis.context().androidContextAsUser(user)
- .getSystemService(DevicePolicyManager.class);
- TestApis.settings().secure().putInt(user, USER_SETUP_COMPLETE_KEY, complete ? 1 : 0);
- try (PermissionContext p =
- TestApis.permissions().withPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)) {
- devicePolicyManager.forceUpdateUserSetupComplete(user.id());
- }
- }
-
- private boolean getUserSetupComplete(UserReference user) {
- try (PermissionContext p = TestApis.permissions().withPermission(CREATE_USERS)) {
- return
- TestApis.settings().secure()
- .getInt(user, USER_SETUP_COMPLETE_KEY, /* def= */ 0) == 1;
- }
- }
-
private DeviceOwner setDeviceOwnerPreS(ComponentName deviceOwnerComponent) {
UserReference user = TestApis.users().system();
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/ProfileOwner.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/ProfileOwner.java
index e475d25..8c21b65 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/ProfileOwner.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/ProfileOwner.java
@@ -16,10 +16,17 @@
package com.android.bedstead.nene.devicepolicy;
-import static com.android.bedstead.nene.permissions.Permissions.MANAGE_PROFILE_AND_DEVICE_OWNERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static com.android.bedstead.nene.permissions.Permissions.MANAGE_PROFILE_AND_DEVICE_OWNERS;
+import static com.android.compatibility.common.util.enterprise.DeviceAdminReceiverUtils.ACTION_DISABLE_SELF;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
+import android.content.Intent;
import android.os.Build;
import com.android.bedstead.nene.TestApis;
@@ -28,10 +35,14 @@
import com.android.bedstead.nene.packages.Package;
import com.android.bedstead.nene.permissions.PermissionContext;
import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.utils.Poll;
+import com.android.bedstead.nene.utils.Retry;
import com.android.bedstead.nene.utils.ShellCommand;
import com.android.bedstead.nene.utils.ShellCommandUtils;
import com.android.bedstead.nene.utils.Versions;
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+import java.time.Duration;
import java.util.Objects;
/**
@@ -39,6 +50,9 @@
*/
public final class ProfileOwner extends DevicePolicyController {
+ private static final String TEST_APP_APP_COMPONENT_FACTORY =
+ "com.android.bedstead.testapp.TestAppAppComponentFactory";
+
ProfileOwner(UserReference user,
Package pkg,
ComponentName componentName) {
@@ -60,7 +74,20 @@
try (PermissionContext p =
TestApis.permissions().withPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)) {
devicePolicyManager.forceRemoveActiveAdmin(mComponentName, mUser.id());
+ } catch (SecurityException e) {
+ if (e.getMessage().contains("Attempt to remove non-test admin")
+ && mPackage.appComponentFactory().equals(TEST_APP_APP_COMPONENT_FACTORY)
+ && user().parent() == null) {
+ removeTestApp();
+ } else {
+ throw e;
+ }
}
+
+ Poll.forValue("Profile Owner",
+ () -> TestApis.devicePolicy().getProfileOwner(mUser))
+ .toBeNull()
+ .errorOnFail().await();
}
private void removePreS() {
@@ -70,7 +97,40 @@
.validate(ShellCommandUtils::startsWithSuccess)
.execute();
} catch (AdbException e) {
- throw new NeneException("Error removing profile owner " + this, e);
+ if (mPackage.appComponentFactory().equals(TEST_APP_APP_COMPONENT_FACTORY)
+ && user().parent() == null) {
+ // We can't see why it failed so we'll try the test app version
+ removeTestApp();
+ } else {
+ throw new NeneException("Error removing profile owner " + this, e);
+ }
+ }
+ }
+
+ private void removeTestApp() {
+ // Special case for removing TestApp DPCs - this works even when not testOnly
+ // but not on profiles
+ Intent intent = new Intent(ACTION_DISABLE_SELF);
+ intent.setComponent(new ComponentName(pkg().packageName(),
+ "com.android.bedstead.testapp.TestAppBroadcastController"));
+
+ try (PermissionContext p =
+ TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+ // If the profile isn't ready then the broadcast won't be sent and the profile owner
+ // will not be removed. So we can retry until the broadcast has been dealt with.
+ Retry.logic(() -> {
+ BlockingBroadcastReceiver b = new BlockingBroadcastReceiver(
+ TestApis.context().instrumentedContext());
+
+ TestApis.context().androidContextAsUser(mUser).sendOrderedBroadcast(
+ intent, /* receiverPermission= */ null, b, /* scheduler= */
+ null, /* initialCode= */
+ Activity.RESULT_CANCELED, /* initialData= */ null, /* initialExtras= */
+ null);
+
+ b.awaitForBroadcastOrFail(Duration.ofSeconds(30).toMillis());
+ assertThat(b.getResultCode()).isEqualTo(Activity.RESULT_OK);
+ }).timeout(Duration.ofMinutes(5)).runAndWrapException();
}
}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java
deleted file mode 100644
index ca3ef6d..0000000
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.bedstead.nene.exceptions;
-
-import androidx.annotation.Nullable;
-
-/**
- * An exception that gets thrown when interacting with Adb.
- */
-public class AdbException extends Exception {
-
- private final String mCommand;
- private final @Nullable String mOutput;
- private final @Nullable String mErr;
-
- public AdbException(String message, String command, String output) {
- this(message, command, output, /* err= */ (String) null);
- }
-
- public AdbException(String message, String command, String output, String err) {
- super(message);
- if (command == null) {
- throw new NullPointerException();
- }
- this.mCommand = command;
- this.mOutput = output;
- this.mErr = err;
- }
-
- public AdbException(String message, String command, Throwable cause) {
- this(message, command, /* output= */ null, cause);
- }
-
- public AdbException(String message, String command, String output, Throwable cause) {
- super(message, cause);
- if (command == null) {
- throw new NullPointerException();
- }
- this.mCommand = command;
- this.mOutput = output;
- this.mErr = null;
- }
-
- public String command() {
- return mCommand;
- }
-
- public String output() {
- return mOutput;
- }
-
- public String error() {
- return mErr;
- }
-
- @Override
- public String toString() {
- StringBuilder stringBuilder = new StringBuilder(super.toString());
-
- stringBuilder.append("[command=\"").append(mCommand).append("\"");
- if (mOutput != null) {
- stringBuilder.append(", output=\"").append(mOutput).append("\"");
- }
- if (mErr != null) {
- stringBuilder.append(", err=\"").append(mErr).append("\"");
- }
- stringBuilder.append("]");
-
- return stringBuilder.toString();
- }
-}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Package.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Package.java
index 3b76374..fc2027f 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Package.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Package.java
@@ -26,11 +26,13 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_DEVELOPMENT;
+import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.S;
import static android.os.Process.myUid;
import static com.google.common.truth.Truth.assertWithMessage;
+import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.Intent;
import android.content.IntentFilter;
@@ -781,18 +783,18 @@
return Packages.parseDumpsys().mPackages.containsKey(mPackageName);
}
+ /** Get the targetSdkVersion for the package. */
+ @Experimental
+ public int targetSdkVersion() {
+ return applicationInfoFromAnyUserOrError(/* flags= */ 0).targetSdkVersion;
+ }
+
/**
* {@code true} if the package is installed in the device's system image.
*/
@Experimental
public boolean hasSystemFlag() {
- ApplicationInfo appInfo = applicationInfoFromAnyUser(/* flags= */ 0);
-
- if (appInfo == null) {
- throw new NeneException("Package not installed: " + this);
- }
-
- return (appInfo.flags & FLAG_SYSTEM) > 0;
+ return (applicationInfoFromAnyUserOrError(/* flags= */ 0).flags & FLAG_SYSTEM) > 0;
}
@Experimental
@@ -800,6 +802,22 @@
return sPackageManager.isInstantApp(mPackageName);
}
+ /** Get the AppComponentFactory for the package. */
+ @Experimental
+ @Nullable
+ @TargetApi(P)
+ public String appComponentFactory() {
+ return applicationInfoFromAnyUserOrError(/* flags= */ 0).appComponentFactory;
+ }
+
+ private ApplicationInfo applicationInfoFromAnyUserOrError(int flags) {
+ ApplicationInfo appInfo = applicationInfoFromAnyUser(flags);
+ if (appInfo == null) {
+ throw new NeneException("Package not installed: " + this);
+ }
+ return appInfo;
+ }
+
/**
* Gets the shared user id of the package.
*/
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/Permissions.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/Permissions.java
index fa797bb..b9c92ff 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/Permissions.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/Permissions.java
@@ -250,7 +250,7 @@
Set<String> adoptedShellPermissions = new HashSet<>();
for (String permission : grantedPermissions) {
- checkCanGrantOnAllSupportedVersions(permission, sUser);
+ checkCanGrantOnAllSupportedVersions(permission);
Log.d(LOG_TAG, "Trying to grant " + permission);
if (sInstrumentedPackage.hasPermission(sUser, permission)) {
@@ -269,7 +269,7 @@
removePermissionContextsUntilCanApply();
throwPermissionException("PermissionContext requires granting "
- + permission + " but cannot.", permission, sUser);
+ + permission + " but cannot.", permission);
}
}
@@ -284,7 +284,7 @@
} else { // We can't deny a permission to ourselves
removePermissionContextsUntilCanApply();
throwPermissionException("PermissionContext requires denying "
- + permission + " but cannot.", permission, sUser);
+ + permission + " but cannot.", permission);
}
}
@@ -296,7 +296,7 @@
}
private void checkCanGrantOnAllSupportedVersions(
- String permission, UserReference user) {
+ String permission) {
if (sCheckedGrantPermissions.contains(permission)) {
return;
}
@@ -310,7 +310,7 @@
+ "possible add it to"
+ "com.android.bedstead.nene.permissions"
+ ".Permissions#EXEMPT_SHELL_PERMISSIONS",
- permission, user);
+ permission);
}
sCheckedGrantPermissions.add(permission);
@@ -325,8 +325,11 @@
sCheckedDenyPermissions.add(permission);
}
- private void throwPermissionException(
- String message, String permission, UserReference user) {
+ /**
+ * Throw an exception including permission contextual information.
+ */
+ public void throwPermissionException(
+ String message, String permission) {
String protectionLevel = "Permission not found";
try {
protectionLevel = Integer.toString(sPackageManager.getPermissionInfo(
@@ -335,7 +338,7 @@
Log.e(LOG_TAG, "Permission not found", e);
}
- throw new NeneException(message + "\n\nRunning On User: " + user
+ throw new NeneException(message + "\n\nRunning On User: " + sUser
+ "\nPermission: " + permission
+ "\nPermission protection level: " + protectionLevel
+ "\nPermission state: " + sContext.checkSelfPermission(permission)
@@ -354,6 +357,13 @@
}
/**
+ * Returns all of the permissions which can be adopted.
+ */
+ public Set<String> adoptablePermissions() {
+ return mShellPermissions;
+ }
+
+ /**
* Returns all of the permissions which are currently able to be used.
*/
public Set<String> usablePermissions() {
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/SecureSettings.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/SecureSettings.java
index b33b402..b1c5891 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/SecureSettings.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/SecureSettings.java
@@ -175,6 +175,138 @@
}
/**
+ * See {@link Settings.Secure#putString(ContentResolver, String, String)}
+ */
+ @RequiresApi(Build.VERSION_CODES.S)
+ public void putString(ContentResolver contentResolver, String key, String value) {
+ Versions.requireMinimumVersion(Build.VERSION_CODES.S);
+ try (PermissionContext p = sTestApis.permissions().withPermission(
+ INTERACT_ACROSS_USERS_FULL, WRITE_SECURE_SETTINGS)) {
+ Settings.Secure.putString(contentResolver, key, value);
+ }
+ }
+
+ /**
+ * Put String to secure settings for the given {@link UserReference}.
+ *
+ * <p>If the user is not the instrumented user, this will only succeed when running on Android S
+ * and above.
+ *
+ * <p>See {@link #putString(ContentResolver, String, String)}
+ */
+ @SuppressLint("NewApi")
+ public void putString(UserReference user, String key, String value) {
+ if (user.equals(sTestApis.users().instrumented())) {
+ putString(key, value);
+ return;
+ }
+
+ putString(sTestApis.context().androidContextAsUser(user).getContentResolver(), key, value);
+ }
+
+ /**
+ * Put String to secure settings for the instrumented user.
+ *
+ * <p>See {@link #putString(ContentResolver, String, String)}
+ */
+ public void putString(String key, String value) {
+ try (PermissionContext p = sTestApis.permissions().withPermission(
+ WRITE_SECURE_SETTINGS)) {
+ Settings.Secure.putString(
+ sTestApis.context().instrumentedContext().getContentResolver(), key, value);
+ }
+ }
+
+ /**
+ * See {@link Settings.Secure#getString(ContentResolver, String)}
+ */
+ @RequiresApi(Build.VERSION_CODES.S)
+ public String getString(ContentResolver contentResolver, String key) {
+ Versions.requireMinimumVersion(Build.VERSION_CODES.S);
+ try (PermissionContext p =
+ sTestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+ return getStringInner(contentResolver, key);
+ }
+ }
+
+ /**
+ * See {@link Settings.Secure#getString(ContentResolver, String)}.
+ *
+ * <p>If the value is null, the {@code defaultValue} will be returned.
+ */
+ @RequiresApi(Build.VERSION_CODES.S)
+ public String getString(ContentResolver contentResolver, String key, String defaultValue) {
+ Versions.requireMinimumVersion(Build.VERSION_CODES.S);
+ try (PermissionContext p =
+ sTestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+ return getStringInner(contentResolver, key, defaultValue);
+ }
+ }
+
+ private String getStringInner(ContentResolver contentResolver, String key) {
+ return Settings.Secure.getString(contentResolver, key);
+ }
+
+ private String getStringInner(
+ ContentResolver contentResolver, String key, String defaultValue) {
+ String s = getStringInner(contentResolver, key);
+ return s == null ? defaultValue : s;
+ }
+
+ /**
+ * Get String from secure settings for the given {@link UserReference}.
+ *
+ * <p>If the user is not the instrumented user, this will only succeed when running on Android S
+ * and above.
+ *
+ * <p>See {@link #getString(ContentResolver, String)}
+ */
+ @SuppressLint("NewApi")
+ public String getString(UserReference user, String key) {
+ if (user.equals(sTestApis.users().instrumented())) {
+ return getString(key);
+ }
+ return getString(sTestApis.context().androidContextAsUser(user).getContentResolver(), key);
+ }
+
+ /**
+ * Get String from secure settings for the given {@link UserReference}, or the default value.
+ *
+ * <p>If the user is not the instrumented user, this will only succeed when running on Android S
+ * and above.
+ *
+ * <p>See {@link #getString(ContentResolver, String, String)}
+ */
+ @SuppressLint("NewApi")
+ public String getString(UserReference user, String key, String defaultValue) {
+ if (user.equals(sTestApis.users().instrumented())) {
+ return getString(key, defaultValue);
+ }
+ return getString(
+ sTestApis.context().androidContextAsUser(user).getContentResolver(),
+ key, defaultValue);
+ }
+
+ /**
+ * Get String from secure settings for the instrumented user.
+ *
+ * <p>See {@link #getString(ContentResolver, String)}
+ */
+ public String getString(String key) {
+ return getStringInner(sTestApis.context().instrumentedContext().getContentResolver(), key);
+ }
+
+ /**
+ * Get String from secure settings for the instrumented user, or the default value.
+ *
+ * <p>See {@link #getString(ContentResolver, String)}
+ */
+ public String getString(String key, String defaultValue) {
+ return getStringInner(
+ sTestApis.context().instrumentedContext().getContentResolver(), key, defaultValue);
+ }
+
+ /**
* Reset all secure settings set by this package.
*
* See {@link Settings.Secure#resetToDefaults(ContentResolver, String)}.
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserBuilder.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserBuilder.java
index 31e2685..c10efed 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserBuilder.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserBuilder.java
@@ -25,6 +25,7 @@
import androidx.annotation.CheckResult;
import androidx.annotation.Nullable;
+import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.exceptions.AdbException;
import com.android.bedstead.nene.exceptions.NeneException;
import com.android.bedstead.nene.utils.ShellCommand;
@@ -138,7 +139,7 @@
commandBuilder.validate(ShellCommandUtils::startsWithSuccess)
.executeAndParseOutput(
(output) -> Integer.parseInt(output.split("id ")[1].trim()));
- return new UserReference(userId);
+ return TestApis.users().find(userId);
} catch (AdbException e) {
throw new NeneException("Could not create user " + this, e);
}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserReference.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserReference.java
index aa9343f..4ca2fce 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserReference.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserReference.java
@@ -22,8 +22,11 @@
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
+import static com.android.bedstead.nene.permissions.CommonPermissions.MANAGE_PROFILE_AND_DEVICE_OWNERS;
import static com.android.bedstead.nene.users.Users.users;
+import android.app.KeyguardManager;
+import android.app.admin.DevicePolicyManager;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.os.UserHandle;
@@ -33,6 +36,7 @@
import androidx.annotation.Nullable;
import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.annotations.Experimental;
import com.android.bedstead.nene.exceptions.AdbException;
import com.android.bedstead.nene.exceptions.NeneException;
import com.android.bedstead.nene.permissions.PermissionContext;
@@ -60,6 +64,8 @@
private static final String LOG_TAG = "UserReference";
+ private static final String USER_SETUP_COMPLETE_KEY = "user_setup_complete";
+
private final int mId;
private final UserManager mUserManager;
@@ -70,6 +76,7 @@
private Boolean mIsPrimary;
private boolean mParentCached = false;
private UserReference mParent;
+ private @Nullable String mPassword;
UserReference(int id) {
mId = id;
@@ -371,6 +378,108 @@
return users().anyMatch(ui -> ui.id == id());
}
+ /**
+ * Sets the value of {@code user_setup_complete} in secure settings to {@code complete}.
+ */
+ @Experimental
+ public void setSetupComplete(boolean complete) {
+ if (!Versions.meetsMinimumSdkVersionRequirement(S)) {
+ return;
+ }
+ DevicePolicyManager devicePolicyManager =
+ TestApis.context().androidContextAsUser(this)
+ .getSystemService(DevicePolicyManager.class);
+ TestApis.settings().secure().putInt(
+ /* user= */ this, USER_SETUP_COMPLETE_KEY, complete ? 1 : 0);
+ try (PermissionContext p =
+ TestApis.permissions().withPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)) {
+ devicePolicyManager.forceUpdateUserSetupComplete(id());
+ }
+ }
+
+ /**
+ * Gets the value of {@code user_setup_complete} from secure settings.
+ */
+ @Experimental
+ public boolean getSetupComplete() {
+ try (PermissionContext p = TestApis.permissions().withPermission(CREATE_USERS)) {
+ return TestApis.settings().secure()
+ .getInt(/*user= */ this, USER_SETUP_COMPLETE_KEY, /* def= */ 0) == 1;
+ }
+ }
+
+ /**
+ * True if the user has a password.
+ */
+ public boolean hasPassword() {
+ return TestApis.context().androidContextAsUser(this)
+ .getSystemService(KeyguardManager.class).isDeviceSecure();
+ }
+
+ /**
+ * Set a password for the user.
+ */
+ public void setPassword(String password) {
+ try {
+ ShellCommand.builder("cmd lock_settings")
+ .addOperand("set-password")
+ .addOperand(password)
+ .addOption("--user", mId)
+ .validate(s -> s.startsWith("Password set to "))
+ .execute();
+ } catch (AdbException e) {
+ throw new NeneException("Error setting password", e);
+ }
+ mPassword = password;
+ }
+
+ /**
+ * Clear the password for the user, using the password that was last set using Nene.
+ */
+ public void clearPassword() {
+ if (mPassword == null) {
+ throw new NeneException(
+ "clearPassword() can only be called when setPassword was used to set the"
+ + " password");
+ }
+ clearPassword(mPassword);
+ }
+
+ /**
+ * Clear the password for the user.
+ */
+ public void clearPassword(String password) {
+
+ try {
+ ShellCommand.builder("cmd lock_settings")
+ .addOperand("clear")
+ .addOption("--old", password)
+ .addOption("--user", mId)
+ .validate(s -> s.startsWith("Lock credential cleared"))
+ .execute();
+ } catch (AdbException e) {
+ if (e.output().contains("user has no password")) {
+ // No password anyway, fine
+ mPassword = null;
+ return;
+ }
+ throw new NeneException("Error clearing password", e);
+ }
+
+ mPassword = null;
+ }
+
+ /**
+ * Returns the password for this user if that password was set using Nene.
+ *
+ *
+ * <p>If there is no password, or the password was not set using Nene, then this will
+ * return {@code null}.
+ */
+ public @Nullable String password() {
+ return mPassword;
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof UserReference)) {
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/Users.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/Users.java
index 8813a46..cf0891a 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/Users.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/Users.java
@@ -58,6 +58,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -73,6 +74,7 @@
private final AdbUserParser mParser;
private static final UserManager sUserManager =
TestApis.context().instrumentedContext().getSystemService(UserManager.class);
+ private Map<Integer, UserReference> mUsers = new ConcurrentHashMap<>();
public static final Users sInstance = new Users();
@@ -89,7 +91,7 @@
}
return users().map(
- ui -> new UserReference(ui.id)
+ ui -> find(ui.id)
).collect(Collectors.toSet());
}
@@ -152,12 +154,15 @@
/** Get a {@link UserReference} by {@code id}. */
public UserReference find(int id) {
- return new UserReference(id);
+ if (!mUsers.containsKey(id)) {
+ mUsers.put(id, new UserReference(id));
+ }
+ return mUsers.get(id);
}
/** Get a {@link UserReference} by {@code userHandle}. */
public UserReference find(UserHandle userHandle) {
- return new UserReference(userHandle.getIdentifier());
+ return find(userHandle.getIdentifier());
}
/** Get all supported {@link UserType}s. */
@@ -328,7 +333,7 @@
id++;
}
- return new UserReference(id);
+ return find(id);
}
private void fillCache() {
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommand.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommand.java
index 7818bcc..4c686d6 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommand.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommand.java
@@ -59,7 +59,6 @@
}
public static final class Builder {
- private String mLinuxUser;
private final StringBuilder commandBuilder;
@Nullable
private byte[] mStdInBytes = null;
@@ -129,9 +128,6 @@
* Build the full command including all options and operands.
*/
public String build() {
- if (mLinuxUser != null) {
- return "su " + mLinuxUser + " " + commandBuilder.toString();
- }
return commandBuilder.toString();
}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/device/DeviceTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/device/DeviceTest.java
new file mode 100644
index 0000000..a660beb
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/device/DeviceTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.nene.device;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.nene.TestApis;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(BedsteadJUnit4.class)
+public final class DeviceTest {
+
+ public static final DeviceState sDeviceState = new DeviceState();
+
+ @Test
+ public void wakeUp_screenTurnsOn() {
+ TestApis.device().wakeUp();
+
+ assertThat(TestApis.device().isScreenOn()).isTrue();
+ }
+}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/DeviceOwnerTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/DeviceOwnerTest.java
index 91932c1..652faff 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/DeviceOwnerTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/DeviceOwnerTest.java
@@ -23,8 +23,12 @@
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDpc;
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.remotedpc.RemoteDpc;
+import com.android.bedstead.testapp.TestApp;
+import com.android.bedstead.testapp.TestAppInstance;
+import com.android.bedstead.testapp.TestAppProvider;
import org.junit.Before;
import org.junit.ClassRule;
@@ -40,6 +44,15 @@
public static DeviceState sDeviceState = new DeviceState();
private static final ComponentName DPC_COMPONENT_NAME = RemoteDpc.DPC_COMPONENT_NAME;
+ private static final TestAppProvider sTestAppProvider = new TestAppProvider();
+ private static final TestApp sNonTestOnlyDpc = sTestAppProvider.query()
+ .whereIsDeviceAdmin().isTrue()
+ .whereTestOnly().isFalse()
+ .get();
+ private static final ComponentName NON_TEST_ONLY_DPC_COMPONENT_NAME = new ComponentName(
+ sNonTestOnlyDpc.packageName(),
+ "com.android.bedstead.testapp.DeviceAdminTestApp.DeviceAdminReceiver"
+ );
private DeviceOwner mDeviceOwner;
@@ -69,4 +82,18 @@
assertThat(TestApis.devicePolicy().getDeviceOwner()).isNull();
}
+
+
+ @Test
+ @EnsureHasNoDpc
+ public void remove_nonTestOnlyDpc_removesDeviceOwner() {
+ try (TestAppInstance dpc = sNonTestOnlyDpc.install()) {
+ DeviceOwner deviceOwner = TestApis.devicePolicy()
+ .setDeviceOwner(NON_TEST_ONLY_DPC_COMPONENT_NAME);
+
+ deviceOwner.remove();
+
+ assertThat(TestApis.devicePolicy().getDeviceOwner()).isNull();
+ }
+ }
}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/ProfileOwnerTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/ProfileOwnerTest.java
index 0bb85a0..39e25d3 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/ProfileOwnerTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/ProfileOwnerTest.java
@@ -22,11 +22,17 @@
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunNotOnSecondaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDpc;
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasProfileOwner;
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.users.UserReference;
import com.android.bedstead.remotedpc.RemoteDpc;
import com.android.bedstead.testapp.TestApp;
+import com.android.bedstead.testapp.TestAppInstance;
+import com.android.bedstead.testapp.TestAppProvider;
import org.junit.Before;
import org.junit.ClassRule;
@@ -38,9 +44,17 @@
public class ProfileOwnerTest {
private static final ComponentName DPC_COMPONENT_NAME = RemoteDpc.DPC_COMPONENT_NAME;
+ private static final TestAppProvider sTestAppProvider = new TestAppProvider();
+ private static final TestApp sNonTestOnlyDpc = sTestAppProvider.query()
+ .whereIsDeviceAdmin().isTrue()
+ .whereTestOnly().isFalse()
+ .get();
+ private static final ComponentName NON_TEST_ONLY_DPC_COMPONENT_NAME = new ComponentName(
+ sNonTestOnlyDpc.packageName(),
+ "com.android.bedstead.testapp.DeviceAdminTestApp.DeviceAdminReceiver"
+ );
+
private static UserReference sProfile;
- private static TestApp sTestApp;
- private static DevicePolicyController sProfileOwner;
@ClassRule @Rule
public static final DeviceState sDeviceState = new DeviceState();
@@ -48,36 +62,71 @@
@Before
public void setUp() {
sProfile = TestApis.users().instrumented();
- sTestApp = sDeviceState.dpc().testApp();
- sProfileOwner = sDeviceState.profileOwner().devicePolicyController();
}
@Test
@EnsureHasProfileOwner
public void user_returnsUser() {
- assertThat(sProfileOwner.user()).isEqualTo(sProfile);
+ assertThat(sDeviceState.profileOwner().devicePolicyController().user()).isEqualTo(sProfile);
}
@Test
@EnsureHasProfileOwner
public void pkg_returnsPackage() {
- assertThat(sProfileOwner.pkg()).isEqualTo(sTestApp.pkg());
+ assertThat(sDeviceState.profileOwner().devicePolicyController().pkg()).isNotNull();
}
@Test
@EnsureHasProfileOwner
public void componentName_returnsComponentName() {
- assertThat(sProfileOwner.componentName()).isEqualTo(DPC_COMPONENT_NAME);
+ assertThat(sDeviceState.profileOwner().devicePolicyController().componentName())
+ .isEqualTo(DPC_COMPONENT_NAME);
}
@Test
@EnsureHasProfileOwner
public void remove_removesProfileOwner() {
- sProfileOwner.remove();
+ sDeviceState.profileOwner().devicePolicyController().remove();
try {
assertThat(TestApis.devicePolicy().getProfileOwner(sProfile)).isNull();
} finally {
- sProfileOwner = TestApis.devicePolicy().setProfileOwner(sProfile, DPC_COMPONENT_NAME);
+ TestApis.devicePolicy().setProfileOwner(sProfile, DPC_COMPONENT_NAME);
}
}
+
+ @Test
+ @EnsureHasNoDpc
+ public void remove_nonTestOnlyDpc_removesProfileOwner() {
+ try (TestAppInstance dpc = sNonTestOnlyDpc.install()) {
+ ProfileOwner profileOwner = TestApis.devicePolicy().setProfileOwner(
+ TestApis.users().instrumented(), NON_TEST_ONLY_DPC_COMPONENT_NAME);
+
+ profileOwner.remove();
+
+ assertThat(TestApis.devicePolicy().getProfileOwner()).isNull();
+ }
+ }
+
+ @Test
+ @EnsureHasSecondaryUser
+ @RequireRunNotOnSecondaryUser
+ public void remove_onOtherUser_removesProfileOwner() {
+ try (TestAppInstance dpc = sNonTestOnlyDpc.install(sDeviceState.secondaryUser())) {
+ ProfileOwner profileOwner = TestApis.devicePolicy().setProfileOwner(
+ sDeviceState.secondaryUser(), NON_TEST_ONLY_DPC_COMPONENT_NAME);
+
+ profileOwner.remove();
+
+ assertThat(TestApis.devicePolicy().getProfileOwner(sDeviceState.secondaryUser()))
+ .isNull();
+ }
+ }
+
+ @Test
+ @RequireRunOnWorkProfile
+ public void remove_onWorkProfile_testDpc_removesProfileOwner() {
+ TestApis.devicePolicy().getProfileOwner().remove();
+
+ assertThat(TestApis.devicePolicy().getProfileOwner()).isNull();
+ }
}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/notifications/NotificationsTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/notifications/NotificationsTest.java
index 1d662fe..0b09802 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/notifications/NotificationsTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/notifications/NotificationsTest.java
@@ -16,6 +16,8 @@
package com.android.bedstead.nene.notifications;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+
import static com.android.bedstead.nene.notifications.NotificationListenerQuerySubject.assertThat;
import static com.android.bedstead.nene.notifications.Notifications.LISTENER_COMPONENT;
@@ -32,6 +34,7 @@
import com.android.bedstead.harrier.DeviceState;
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.nene.permissions.PermissionContext;
import com.android.bedstead.nene.users.UserReference;
import org.junit.ClassRule;
@@ -101,16 +104,18 @@
}
private void createNotification() {
- String channelId = "notifications";
- sNotificationManager.createNotificationChannel(new NotificationChannel(channelId,
- "notifications",
- NotificationChannel.USER_LOCKED_IMPORTANCE));
+ try (PermissionContext p = TestApis.permissions().withPermission(POST_NOTIFICATIONS)) {
+ String channelId = "notifications";
+ sNotificationManager.createNotificationChannel(new NotificationChannel(channelId,
+ "notifications",
+ NotificationChannel.USER_LOCKED_IMPORTANCE));
- Notification.Builder notificationBuilder =
- new Notification.Builder(TestApis.context().instrumentedContext(), channelId)
- .setSmallIcon(R.drawable.sym_def_app_icon);
+ Notification.Builder notificationBuilder =
+ new Notification.Builder(TestApis.context().instrumentedContext(), channelId)
+ .setSmallIcon(R.drawable.sym_def_app_icon);
- sNotificationManager.notify(1, notificationBuilder.build());
+ sNotificationManager.notify(1, notificationBuilder.build());
+ }
}
}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/settings/SecureSettingsTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/settings/SecureSettingsTest.java
index 7a2d164..ae7dcef 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/settings/SecureSettingsTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/settings/SecureSettingsTest.java
@@ -52,6 +52,7 @@
private static final String KEY = "key";
private static final String INVALID_KEY = "noKey";
private static final int INT_VALUE = 123;
+ private static final String STRING_VALUE = "String";
@After
public void resetSecureSettings() {
@@ -207,6 +208,153 @@
});
}
+ @Test
+ public void putString_putsStringIntoSecureSettingsOnInstrumentedUser() throws Exception {
+ TestApis.settings().secure().putString(KEY, STRING_VALUE);
+
+ assertThat(android.provider.Settings.Secure.getString(sContext.getContentResolver(), KEY))
+ .isEqualTo(STRING_VALUE);
+ }
+
+ @Test
+ @RequireSdkVersion(min = Build.VERSION_CODES.S)
+ public void putStringWithContentResolver_putsStringIntoSecureSettings() throws Exception {
+ TestApis.settings().secure().putString(sContext.getContentResolver(), KEY, STRING_VALUE);
+
+ assertThat(android.provider.Settings.Secure.getString(sContext.getContentResolver(), KEY))
+ .isEqualTo(STRING_VALUE);
+ }
+
+ @RequireSdkVersion(max = Build.VERSION_CODES.R)
+ public void putStringWithContentResolver_preS_throwsException() throws Exception {
+ assertThrows(UnsupportedOperationException.class, () ->
+ TestApis.settings().secure().putString(
+ sContext.getContentResolver(), KEY, STRING_VALUE));
+ }
+
+ @Test
+ public void putStringWithUser_instrumentedUser_putsStringIntoSecureSettings() throws Exception {
+ TestApis.settings().secure().putString(TestApis.users().instrumented(), KEY, STRING_VALUE);
+
+ assertThat(android.provider.Settings.Secure.getString(sContext.getContentResolver(), KEY))
+ .isEqualTo(STRING_VALUE);
+ }
+
+ @Test
+ @EnsureHasSecondaryUser
+ @RequireSdkVersion(min = Build.VERSION_CODES.S)
+ public void putStringWithUser_differentUser_putsStringIntoSecureSettings() throws Exception {
+ TestApis.settings().secure().putString(sDeviceState.secondaryUser(), KEY, STRING_VALUE);
+
+ try (PermissionContext p =
+ TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+ assertThat(android.provider.Settings.Secure.getString(
+ TestApis.context().androidContextAsUser(sDeviceState.secondaryUser())
+ .getContentResolver(), KEY)).isEqualTo(STRING_VALUE);
+ }
+ }
+
+ @Test
+ @EnsureHasSecondaryUser
+ @RequireSdkVersion(max = Build.VERSION_CODES.R)
+ public void putStringWithUser_differentUser_preS_throwsException() throws Exception {
+ assertThrows(UnsupportedOperationException.class, () ->
+ TestApis.settings().secure()
+ .putString(sDeviceState.secondaryUser(), KEY, STRING_VALUE));
+ }
+
+ @Test
+ public void getString_getsStringFromSecureSettingsOnInstrumentedUser() {
+ TestApis.settings().secure().putString(KEY, STRING_VALUE);
+
+ assertThat(TestApis.settings().secure().getString(KEY)).isEqualTo(STRING_VALUE);
+ }
+
+ @Test
+ public void getString_invalidKey_returnsNull() {
+ assertThat(TestApis.settings().secure().getString(INVALID_KEY)).isNull();
+ }
+
+ @Test
+ public void getString_invalidKey_withDefault_returnsDefault() {
+ assertThat(TestApis.settings().secure().getString(INVALID_KEY, STRING_VALUE)).isEqualTo(
+ STRING_VALUE);
+ }
+
+ @Test
+ @RequireSdkVersion(min = Build.VERSION_CODES.S)
+ public void getStringWithContentResolver_getsStringFromSecureSettings() {
+ TestApis.settings().secure().putString(
+ TestApis.context().instrumentedContext().getContentResolver(), KEY, STRING_VALUE);
+
+ assertThat(TestApis.settings().secure().getString(
+ TestApis.context().instrumentedContext().getContentResolver(), KEY))
+ .isEqualTo(STRING_VALUE);
+ }
+
+ @Test
+ @RequireSdkVersion(max = Build.VERSION_CODES.R)
+ public void getStringWithContentResolver_preS_throwsException() {
+ assertThrows(UnsupportedOperationException.class, () -> TestApis.settings().secure()
+ .getString(
+ TestApis.context().instrumentedContext().getContentResolver(), KEY));
+ }
+
+ @Test
+ @RequireSdkVersion(min = Build.VERSION_CODES.S)
+ public void getStringWithContentResolver_invalidKey_returnsNull() {
+ assertThat(TestApis.settings().secure().getString(
+ TestApis.context().instrumentedContext().getContentResolver(),
+ INVALID_KEY)).isNull();
+ }
+
+ @Test
+ @RequireSdkVersion(min = Build.VERSION_CODES.S)
+ public void getStringWithContentResolver_invalidKey_withDefault_returnsDefault() {
+ assertThat(TestApis.settings().secure().getString(
+ TestApis.context().instrumentedContext().getContentResolver(),
+ INVALID_KEY, STRING_VALUE)).isEqualTo(STRING_VALUE);
+ }
+
+ @Test
+ public void getStringWithUser_instrumentedUser_getsStringFromSecureSettings() {
+ TestApis.settings().secure().putString(KEY, STRING_VALUE);
+
+ assertThat(TestApis.settings().secure().getString(TestApis.users().instrumented(), KEY))
+ .isEqualTo(STRING_VALUE);
+ }
+
+ @Test
+ public void getStringWithUser_invalidKey_returnsNull() {
+ assertThat(TestApis.settings().secure().getString(
+ TestApis.users().instrumented(), INVALID_KEY)).isNull();
+ }
+
+ @Test
+ public void getStringWithUser_invalidKey_withDefault_returnsDefault() {
+ assertThat(TestApis.settings().secure().getString(
+ TestApis.users().instrumented(), INVALID_KEY, STRING_VALUE))
+ .isEqualTo(STRING_VALUE);
+ }
+
+ @Test
+ @EnsureHasSecondaryUser
+ @RequireSdkVersion(min = Build.VERSION_CODES.S)
+ public void getStringWithUser_differentUser_getsStringFromSecureSettings() {
+ TestApis.settings().secure().putString(sDeviceState.secondaryUser(), KEY, STRING_VALUE);
+
+ assertThat(TestApis.settings().secure().getString(
+ sDeviceState.secondaryUser(), KEY)).isEqualTo(STRING_VALUE);
+ }
+
+ @Test
+ @EnsureHasSecondaryUser
+ @RequireSdkVersion(max = Build.VERSION_CODES.R)
+ public void getStringWithUser_differentUser_preS_throwsException() {
+ assertThrows(UnsupportedOperationException.class, () -> {
+ TestApis.settings().secure().putString(sDeviceState.secondaryUser(), KEY, STRING_VALUE);
+ });
+ }
// TODO(b/201319369): this requires a system user but should not
@RequireRunOnSystemUser
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java
index 4fbae6a..943aa4f 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java
@@ -35,6 +35,7 @@
import com.android.bedstead.harrier.annotations.EnsureHasPermission;
import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.EnsurePasswordNotSet;
import com.android.bedstead.harrier.annotations.RequireRunNotOnSecondaryUser;
import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
@@ -51,12 +52,11 @@
public class UserReferenceTest {
private static final int NON_EXISTING_USER_ID = 10000;
private static final int USER_ID = NON_EXISTING_USER_ID;
- private static final String USER_NAME = "userName";
private static final String TEST_ACTIVITY_NAME = "com.android.bedstead.nene.test.Activity";
- private static final int SERIAL_NO = 1000;
- private static final UserType USER_TYPE = new UserType(new UserType.MutableUserType());
private static final Context sContext = TestApis.context().instrumentedContext();
private static final UserManager sUserManager = sContext.getSystemService(UserManager.class);
+ private static final String PASSWORD = "1234";
+ private static final String DIFFERENT_PASSWORD = "2345";
@ClassRule @Rule
public static final DeviceState sDeviceState = new DeviceState();
@@ -331,4 +331,59 @@
assertThat(TestApis.users().all()).hasSize(numUsers);
}
+
+ @Test
+ @EnsurePasswordNotSet
+ public void setPassword_hasPassword() {
+ try {
+ TestApis.users().instrumented().setPassword(PASSWORD);
+
+ assertThat(TestApis.users().instrumented().hasPassword()).isTrue();
+ } finally {
+ TestApis.users().instrumented().clearPassword(PASSWORD);
+ }
+ }
+
+ @Test
+ @EnsurePasswordNotSet
+ public void clearPassword_doesNotHavePassword() {
+ TestApis.users().instrumented().setPassword(PASSWORD);
+ TestApis.users().instrumented().clearPassword(PASSWORD);
+
+ assertThat(TestApis.users().instrumented().hasPassword()).isFalse();
+ }
+
+ @Test
+ @EnsurePasswordNotSet
+ public void clearPassword_doesNotHavePassword_doesNothing() {
+ TestApis.users().instrumented().clearPassword(PASSWORD);
+
+ assertThat(TestApis.users().instrumented().hasPassword()).isFalse();
+ }
+
+ @Test
+ @EnsurePasswordNotSet
+ public void clearPassword_incorrectOldPassword_throwsException() {
+ try {
+ TestApis.users().instrumented().setPassword(PASSWORD);
+
+ assertThrows(NeneException.class,
+ () -> TestApis.users().instrumented().clearPassword(DIFFERENT_PASSWORD));
+ } finally {
+ TestApis.users().instrumented().clearPassword(PASSWORD);
+ }
+ }
+
+ @Test
+ @EnsurePasswordNotSet
+ public void setPassword_alreadyHasPassword_throwsException() {
+ try {
+ TestApis.users().instrumented().setPassword(PASSWORD);
+
+ assertThrows(NeneException.class,
+ () -> TestApis.users().instrumented().setPassword(DIFFERENT_PASSWORD));
+ } finally {
+ TestApis.users().instrumented().clearPassword(PASSWORD);
+ }
+ }
}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UsersTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UsersTest.java
index 1769a01..d166477 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UsersTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UsersTest.java
@@ -21,8 +21,8 @@
import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE;
import static android.os.Build.VERSION.SDK_INT;
-import static com.android.bedstead.harrier.DeviceState.UserType.SYSTEM_USER;
import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.UserType.SYSTEM_USER;
import static com.android.bedstead.nene.users.UserType.MANAGED_PROFILE_TYPE_NAME;
import static com.android.bedstead.nene.users.UserType.SECONDARY_USER_TYPE_NAME;
import static com.android.bedstead.nene.users.UserType.SYSTEM_USER_TYPE_NAME;
diff --git a/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/Queryable.java b/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/Queryable.java
index 15df572..aea1a1a 100644
--- a/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/Queryable.java
+++ b/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/Queryable.java
@@ -16,8 +16,6 @@
package com.android.queryable;
-import androidx.annotation.Nullable;
-
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;
@@ -28,9 +26,9 @@
*
* <p>For example, if {@code fieldName} was age, we might generate "age > 5, age < 10"
*/
- @Nullable
String describeQuery(String fieldName);
+
/**
* Join sub-parts of a query for use in {@link #describeQuery(String)}.
*
diff --git a/common/device-side/bedstead/remotedpc/src/main/java/com/android/bedstead/remotedpc/RemoteDpc.java b/common/device-side/bedstead/remotedpc/src/main/java/com/android/bedstead/remotedpc/RemoteDpc.java
index 01519a0..3dc6109 100644
--- a/common/device-side/bedstead/remotedpc/src/main/java/com/android/bedstead/remotedpc/RemoteDpc.java
+++ b/common/device-side/bedstead/remotedpc/src/main/java/com/android/bedstead/remotedpc/RemoteDpc.java
@@ -35,7 +35,7 @@
import com.android.bedstead.testapp.TestAppProvider;
/** Entry point to RemoteDPC. */
-public final class RemoteDpc extends RemotePolicyManager {
+public class RemoteDpc extends RemotePolicyManager {
public static final ComponentName DPC_COMPONENT_NAME = new ComponentName(
"com.android.RemoteDPC",
@@ -231,7 +231,7 @@
private final DevicePolicyController mDevicePolicyController;
- private RemoteDpc(DevicePolicyController devicePolicyController) {
+ RemoteDpc(DevicePolicyController devicePolicyController) {
super(sTestApp, devicePolicyController == null ? null : devicePolicyController.user());
mDevicePolicyController = devicePolicyController;
}
diff --git a/common/device-side/bedstead/remotedpc/src/main/java/com/android/bedstead/remotedpc/RemoteDpcUsingParentInstance.java b/common/device-side/bedstead/remotedpc/src/main/java/com/android/bedstead/remotedpc/RemoteDpcUsingParentInstance.java
new file mode 100644
index 0000000..0dd4e18
--- /dev/null
+++ b/common/device-side/bedstead/remotedpc/src/main/java/com/android/bedstead/remotedpc/RemoteDpcUsingParentInstance.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.remotedpc;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.RemoteDevicePolicyManager;
+import android.content.ComponentName;
+
+import com.android.bedstead.nene.devicepolicy.DevicePolicyController;
+
+/**
+ * Version of {@link RemoteDpc} which returns the result of calling
+ * {@link DevicePolicyManager#getParentProfileInstance(ComponentName)} when calling
+ * {@link #devicePolicyManager()}.
+ */
+public final class RemoteDpcUsingParentInstance extends RemoteDpc {
+
+ public RemoteDpcUsingParentInstance(
+ DevicePolicyController devicePolicyController) {
+ super(devicePolicyController);
+ }
+
+ @Override
+ public RemoteDevicePolicyManager devicePolicyManager() {
+ return super.devicePolicyManager().getParentProfileInstance(componentName());
+ }
+}
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Apis.java b/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Apis.java
index 0a6bafb..75c5ddb 100644
--- a/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Apis.java
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Apis.java
@@ -137,7 +137,11 @@
try {
// Strip "method" and semicolon
methodLine = methodLine.substring(7, methodLine.length() - 1);
- methodSignatures.add(MethodSignature.forApiString(methodLine, types, elements));
+ MethodSignature signature =
+ MethodSignature.forApiString(methodLine, types, elements);
+ if (signature != null) {
+ methodSignatures.add(signature);
+ }
} catch (RuntimeException e) {
throw new IllegalStateException("Error parsing method " + line, e);
}
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/MethodSignature.java b/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/MethodSignature.java
index aa10a8c..0735b68 100644
--- a/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/MethodSignature.java
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/MethodSignature.java
@@ -70,7 +70,8 @@
/**
* Create a {@link MethodSignature} for the given string from an API file.
*/
- public static MethodSignature forApiString(String string, Types types, Elements elements) {
+ public static /* @Nullable */ MethodSignature forApiString(
+ String string, Types types, Elements elements) {
// Strip annotations
string = string.replaceAll("@\\w+?\\(.+?\\) ", "");
string = string.replaceAll("@.+? ", "");
@@ -93,6 +94,11 @@
parts = string.split(" ", 2);
}
+ if (string.startsWith("<")) {
+ // This includes type arguments, for now we ignore this method
+ return null;
+ }
+
returnType = typeForString(parts[0], types, elements);
string = parts[1];
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Processor.java b/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Processor.java
index d93ff7e..ff35f1e 100644
--- a/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Processor.java
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Processor.java
@@ -38,6 +38,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -59,8 +60,10 @@
*
* <p>This is started by including the {@link RemoteFrameworkClasses} annotation.
*
- * <p>For each entry in {@code FRAMEWORK_CLASSES} this will generate an interface including all public
- * and test APIs with the {@code CrossUser} annotation. This interface will be named the same as the
+ * <p>For each entry in {@code FRAMEWORK_CLASSES} this will generate an interface including all
+ * public
+ * and test APIs with the {@code CrossUser} annotation. This interface will be named the same as
+ * the
* framework class except with a prefix of "Remote", and will be in the same package.
*
* <p>This will also generate an implementation of the interface which takes an instance of the
@@ -223,6 +226,85 @@
+ ".PackageManager.OnChecksumsReadyListener) throws java.security.cert"
+ ".CertificateEncodingException, android.content.pm.PackageManager"
+ ".NameNotFoundException",
+ // Uses ComponentInfoFlags
+ "public android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content"
+ + ".ComponentName, @NonNull android.content.pm.PackageManager"
+ + ".ComponentInfoFlags) throws android.content.pm.PackageManager"
+ + ".NameNotFoundException",
+ "public android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content"
+ + ".ComponentName, @NonNull android.content.pm.PackageManager"
+ + ".ComponentInfoFlags) throws android.content.pm.PackageManager"
+ + ".NameNotFoundException",
+ "public android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content"
+ + ".ComponentName, @NonNull android.content.pm.PackageManager"
+ + ".ComponentInfoFlags) throws android.content.pm.PackageManager"
+ + ".NameNotFoundException",
+ "public android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content"
+ + ".ComponentName, @NonNull android.content.pm.PackageManager"
+ + ".ComponentInfoFlags) throws android.content.pm.PackageManager"
+ + ".NameNotFoundException",
+ "public java.util.List<android.content.pm.ProviderInfo> queryContentProviders"
+ + "(@Nullable String, int, @NonNull android.content.pm.PackageManager"
+ + ".ComponentInfoFlags)",
+ "public android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, "
+ + "@NonNull android.content.pm.PackageManager.ComponentInfoFlags)",
+
+ // Uses ApplicationInfoFlags
+ "public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, "
+ + "@NonNull android.content.pm.PackageManager.ApplicationInfoFlags) throws "
+ + "android.content.pm.PackageManager.NameNotFoundException",
+ "public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications"
+ + "(@NonNull android.content.pm.PackageManager.ApplicationInfoFlags)",
+ "public java.util.List<android.content.pm.ApplicationInfo> "
+ + "getInstalledApplicationsAsUser(@NonNull android.content.pm.PackageManager"
+ + ".ApplicationInfoFlags, int)",
+ // Uses PackageInfoFlags
+ "public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(@NonNull "
+ + "android.content.pm.PackageManager.PackageInfoFlags)",
+ "public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, "
+ + "@NonNull android.content.pm.PackageManager.PackageInfoFlags)",
+ "public int[] getPackageGids(@NonNull String, @NonNull android.content.pm"
+ + ".PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager"
+ + ".NameNotFoundException",
+ "public android.content.pm.PackageInfo getPackageInfo(@NonNull String, @NonNull "
+ + "android.content.pm.PackageManager.PackageInfoFlags) throws android.content"
+ + ".pm.PackageManager.NameNotFoundException",
+ "public android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm"
+ + ".VersionedPackage, @NonNull android.content.pm.PackageManager"
+ + ".PackageInfoFlags) throws android.content.pm.PackageManager"
+ + ".NameNotFoundException",
+ "public int getPackageUid(@NonNull String, @NonNull android.content.pm.PackageManager"
+ + ".PackageInfoFlags) throws android.content.pm.PackageManager"
+ + ".NameNotFoundException",
+ "public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions"
+ + "(@NonNull String[], @NonNull android.content.pm.PackageManager"
+ + ".PackageInfoFlags)",
+ "public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries"
+ + "(@NonNull android.content.pm.PackageManager.PackageInfoFlags)",
+
+ // Uses ResolveInfoFlags
+ "public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers"
+ + "(@NonNull android.content.Intent, @NonNull android.content.pm"
+ + ".PackageManager.ResolveInfoFlags)",
+ "public java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull"
+ + " android.content.Intent, @NonNull android.content.pm.PackageManager"
+ + ".ResolveInfoFlags)",
+
+ "public java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions"
+ + "(@Nullable android.content.ComponentName, @Nullable java.util.List<android"
+ + ".content.Intent>, @NonNull android.content.Intent, @NonNull android"
+ + ".content.pm.PackageManager.ResolveInfoFlags)",
+ "public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders"
+ + "(@NonNull android.content.Intent, @NonNull android.content.pm"
+ + ".PackageManager.ResolveInfoFlags)",
+ "public java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull "
+ + "android.content.Intent, @NonNull android.content.pm.PackageManager"
+ + ".ResolveInfoFlags)",
+ "public android.content.pm.ResolveInfo resolveActivity(@NonNull android.content"
+ + ".Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags)",
+ "public android.content.pm.ResolveInfo resolveService(@NonNull android.content"
+ + ".Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags)",
+
// CrossProfileApps
@@ -245,9 +327,9 @@
//Uses Drawable
"public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable("
- + "android.content.pm.ShortcutInfo, int)",
+ + "android.content.pm.ShortcutInfo, int)",
"public android.graphics.drawable.Drawable getShortcutIconDrawable("
- + "android.content.pm.ShortcutInfo, int)",
+ + "android.content.pm.ShortcutInfo, int)",
//Uses Executor
"public void registerPackageInstallerSessionCallback("
@@ -276,36 +358,78 @@
// AccountManager
// Uses Activity
- "public android.accounts.AccountManagerFuture<android.os.Bundle> addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<android.os.Bundle> finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<android.os.Bundle> editProperties(String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthTokenByFeatures(String, String, String[], android.app.Activity, android.os.Bundle, android.os.Bundle, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<android.os.Bundle> removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<android.os.Bundle> startAddAccountSession(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<android.os.Bundle> startUpdateCredentialsSession(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<android.os.Bundle> updateCredentials(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<android.os.Bundle> confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+ "public android.accounts.AccountManagerFuture<android.os.Bundle> finishSession"
+ + "(android.os.Bundle, android.app.Activity, android.accounts"
+ + ".AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+ "public android.accounts.AccountManagerFuture<android.os.Bundle> editProperties"
+ + "(String, android.app.Activity, android.accounts"
+ + ".AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+ "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android"
+ + ".accounts.Account, String, android.os.Bundle, android.app.Activity, "
+ + "android.accounts.AccountManagerCallback<android.os.Bundle>, android.os"
+ + ".Handler)",
+ "public android.accounts.AccountManagerFuture<android.os.Bundle> "
+ + "getAuthTokenByFeatures(String, String, String[], android.app.Activity, "
+ + "android.os.Bundle, android.os.Bundle, android.accounts"
+ + ".AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+ "public android.accounts.AccountManagerFuture<android.os.Bundle> "
+ + "startAddAccountSession(String, String, String[], android.os.Bundle, "
+ + "android.app.Activity, android.accounts.AccountManagerCallback<android.os"
+ + ".Bundle>, android.os.Handler)",
+ "public android.accounts.AccountManagerFuture<android.os.Bundle> "
+ + "startUpdateCredentialsSession(android.accounts.Account, String, android.os"
+ + ".Bundle, android.app.Activity, android.accounts"
+ + ".AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+ "public android.accounts.AccountManagerFuture<android.os.Bundle> updateCredentials"
+ + "(android.accounts.Account, String, android.os.Bundle, android.app"
+ + ".Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, "
+ + "android.os.Handler)",
+ "public android.accounts.AccountManagerFuture<android.os.Bundle> confirmCredentials"
+ + "(android.accounts.Account, android.os.Bundle, android.app.Activity, "
+ + "android.accounts.AccountManagerCallback<android.os.Bundle>, android.os"
+ + ".Handler)",
// Uses OnAccountsUpdateListener
- "public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean)",
- "public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean, String[])",
- "public void removeOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener)",
+ "public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, "
+ + "android.os.Handler, boolean)",
+ "public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, "
+ + "android.os.Handler, boolean, String[])",
+ "public void removeOnAccountsUpdatedListener(android.accounts"
+ + ".OnAccountsUpdateListener)",
// Uses AccountManagerCallback
- "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android.accounts.Account, String, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android.accounts.Account, String, android.os.Bundle, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<android.os.Bundle> addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android.accounts.Account, String, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android.accounts.Account, String, android.os.Bundle, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
- "public android.os.Bundle hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
- "public android.os.Bundle isCredentialsUpdateSuggested(android.accounts.Account, String, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<android.accounts.Account[]> getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<java.lang.Boolean> hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
- "public android.os.Bundle isCredentialsUpdateSuggested(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, String) throws android.accounts.NetworkErrorException",
- "public android.accounts.AccountManagerFuture<java.lang.Boolean> isCredentialsUpdateSuggested(android.accounts.Account, String, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<java.lang.Boolean> removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
- "public android.accounts.AccountManagerFuture<android.accounts.Account> renameAccount(android.accounts.Account, @Size(min=1) String, android.accounts.AccountManagerCallback<android.accounts.Account>, android.os.Handler)",
+ "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android"
+ + ".accounts.Account, String, boolean, android.accounts"
+ + ".AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+ "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android"
+ + ".accounts.Account, String, android.os.Bundle, boolean, android.accounts"
+ + ".AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+ "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android"
+ + ".accounts.Account, String, boolean, android.accounts"
+ + ".AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+ "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android"
+ + ".accounts.Account, String, android.os.Bundle, boolean, android.accounts"
+ + ".AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+ "public android.os.Bundle hasFeatures(android.accounts.Account, String[], android"
+ + ".accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
+ "public android.os.Bundle isCredentialsUpdateSuggested(android.accounts.Account, "
+ + "String, android.accounts.AccountManagerCallback<java.lang.Boolean>, "
+ + "android.os.Handler)",
+ "public android.accounts.AccountManagerFuture<android.accounts.Account[]> "
+ + "getAccountsByTypeAndFeatures(String, String[], android.accounts"
+ + ".AccountManagerCallback<android.accounts.Account[]>, android.os.Handler)",
+ "public android.accounts.AccountManagerFuture<java.lang.Boolean> hasFeatures(android"
+ + ".accounts.Account, String[], android.accounts.AccountManagerCallback<java"
+ + ".lang.Boolean>, android.os.Handler)",
+ "public android.os.Bundle isCredentialsUpdateSuggested(android.accounts"
+ + ".AccountAuthenticatorResponse, android.accounts.Account, String) throws "
+ + "android.accounts.NetworkErrorException",
+ "public android.accounts.AccountManagerFuture<java.lang.Boolean> "
+ + "isCredentialsUpdateSuggested(android.accounts.Account, String, android"
+ + ".accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
+ "public android.accounts.AccountManagerFuture<android.accounts.Account> renameAccount"
+ + "(android.accounts.Account, @Size(min=1) String, android.accounts"
+ + ".AccountManagerCallback<android.accounts.Account>, android.os.Handler)",
// Uses android.accounts.AccountManager
"public static android.accounts.AccountManager get(android.content.Context)",
@@ -316,9 +440,13 @@
"public void addContentView(android.view.View, android.view.ViewGroup.LayoutParams)",
"@Nullable public android.view.View getCurrentFocus()",
"@Nullable public android.view.View onCreatePanelView(int)",
- "@Nullable public android.view.View onCreateView(@NonNull String, @NonNull android.content.Context, @NonNull android.util.AttributeSet)",
- "@Nullable public android.view.View onCreateView(@Nullable android.view.View, @NonNull String, @NonNull android.content.Context, @NonNull android.util.AttributeSet)",
- "public boolean onPreparePanel(int, @Nullable android.view.View, @NonNull android.view.Menu)",
+ "@Nullable public android.view.View onCreateView(@NonNull String, @NonNull android"
+ + ".content.Context, @NonNull android.util.AttributeSet)",
+ "@Nullable public android.view.View onCreateView(@Nullable android.view.View, "
+ + "@NonNull String, @NonNull android.content.Context, @NonNull android.util"
+ + ".AttributeSet)",
+ "public boolean onPreparePanel(int, @Nullable android.view.View, @NonNull android"
+ + ".view.Menu)",
"public void openContextMenu(android.view.View)",
"public void registerForContextMenu(android.view.View)",
"public void setContentView(android.view.View)",
@@ -326,19 +454,29 @@
"public void unregisterForContextMenu(android.view.View)",
// Uses java.io.FileDescriptor
- "public void dump(@NonNull String, @Nullable java.io.FileDescriptor, @NonNull java.io.PrintWriter, @Nullable String[])",
+ "public void dump(@NonNull String, @Nullable java.io.FileDescriptor, @NonNull java.io"
+ + ".PrintWriter, @Nullable String[])",
// Uses android.app.Activity
"@Deprecated public void finishActivityFromChild(@NonNull android.app.Activity, int)",
"@Deprecated public void finishFromChild(android.app.Activity)",
"public final android.app.Activity getParent()",
- "@Deprecated public boolean navigateUpToFromChild(android.app.Activity, android.content.Intent)",
+ "@Deprecated public boolean navigateUpToFromChild(android.app.Activity, android"
+ + ".content.Intent)",
"protected void onChildTitleChanged(android.app.Activity, CharSequence)",
"@Deprecated public boolean onNavigateUpFromChild(android.app.Activity)",
- "@Deprecated public void startActivityFromChild(@NonNull android.app.Activity, @RequiresPermission android.content.Intent, int)",
- "@Deprecated public void startActivityFromChild(@NonNull android.app.Activity, @RequiresPermission android.content.Intent, int, @Nullable android.os.Bundle)",
- "@Deprecated public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException",
- "@Deprecated public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int, @Nullable android.os.Bundle) throws android.content.IntentSender.SendIntentException",
+ "@Deprecated public void startActivityFromChild(@NonNull android.app.Activity, "
+ + "@RequiresPermission android.content.Intent, int)",
+ "@Deprecated public void startActivityFromChild(@NonNull android.app.Activity, "
+ + "@RequiresPermission android.content.Intent, int, @Nullable android.os"
+ + ".Bundle)",
+ "@Deprecated public void startIntentSenderFromChild(android.app.Activity, android"
+ + ".content.IntentSender, int, android.content.Intent, int, int, int) throws "
+ + "android.content.IntentSender.SendIntentException",
+ "@Deprecated public void startIntentSenderFromChild(android.app.Activity, android"
+ + ".content.IntentSender, int, android.content.Intent, int, int, int, "
+ + "@Nullable android.os.Bundle) throws android.content.IntentSender"
+ + ".SendIntentException",
// Uses android.app.ActionBar
"@Nullable public android.app.ActionBar getActionBar()",
@@ -348,8 +486,11 @@
// Uses android.app.Fragment
"@Deprecated public void onAttachFragment(android.app.Fragment)",
- "@Deprecated public void startActivityFromFragment(@NonNull android.app.Fragment, @RequiresPermission android.content.Intent, int)",
- "@Deprecated public void startActivityFromFragment(@NonNull android.app.Fragment, @RequiresPermission android.content.Intent, int, @Nullable android.os.Bundle)",
+ "@Deprecated public void startActivityFromFragment(@NonNull android.app.Fragment, "
+ + "@RequiresPermission android.content.Intent, int)",
+ "@Deprecated public void startActivityFromFragment(@NonNull android.app.Fragment, "
+ + "@RequiresPermission android.content.Intent, int, @Nullable android.os"
+ + ".Bundle)",
// Uses android.app.FragmentManager
"@Deprecated public android.app.FragmentManager getFragmentManager()",
@@ -396,17 +537,22 @@
"public android.view.WindowManager getWindowManager()",
// Uses android.database.Cursor
- "@Deprecated public final android.database.Cursor managedQuery(android.net.Uri, String[], String, String[], String)",
+ "@Deprecated public final android.database.Cursor managedQuery(android.net.Uri, "
+ + "String[], String, String[], String)",
"@Deprecated public void startManagingCursor(android.database.Cursor)",
"@Deprecated public void stopManagingCursor(android.database.Cursor)",
// Uses android.view.ActionMode
"@CallSuper public void onActionModeFinished(android.view.ActionMode)",
"@CallSuper public void onActionModeStarted(android.view.ActionMode)",
- "@Nullable public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback)",
- "@Nullable public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int)",
- "@Nullable public android.view.ActionMode startActionMode(android.view.ActionMode.Callback)",
- "@Nullable public android.view.ActionMode startActionMode(android.view.ActionMode.Callback, int)",
+ "@Nullable public android.view.ActionMode onWindowStartingActionMode(android.view"
+ + ".ActionMode.Callback)",
+ "@Nullable public android.view.ActionMode onWindowStartingActionMode(android.view"
+ + ".ActionMode.Callback, int)",
+ "@Nullable public android.view.ActionMode startActionMode(android.view.ActionMode"
+ + ".Callback)",
+ "@Nullable public android.view.ActionMode startActionMode(android.view.ActionMode"
+ + ".Callback, int)",
// Uses android.view.MenuItem
"public boolean onContextItemSelected(@NonNull android.view.MenuItem)",
@@ -415,13 +561,16 @@
"public boolean onOptionsItemSelected(@NonNull android.view.MenuItem)",
// Uses android.view.ContextMenu
- "public void onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo)",
+ "public void onCreateContextMenu(android.view.ContextMenu, android.view.View, android"
+ + ".view.ContextMenu.ContextMenuInfo)",
// Uses android.app.Dialog
"@Deprecated protected android.app.Dialog onCreateDialog(int)",
- "@Deprecated @Nullable protected android.app.Dialog onCreateDialog(int, android.os.Bundle)",
+ "@Deprecated @Nullable protected android.app.Dialog onCreateDialog(int, android.os"
+ + ".Bundle)",
"@Deprecated protected void onPrepareDialog(int, android.app.Dialog)",
- "@Deprecated protected void onPrepareDialog(int, android.app.Dialog, android.os.Bundle)",
+ "@Deprecated protected void onPrepareDialog(int, android.app.Dialog, android.os"
+ + ".Bundle)",
// Uses android.app.TaskStackBuilder
"public void onCreateNavigateUpTaskStack(android.app.TaskStackBuilder)",
@@ -436,18 +585,24 @@
"public boolean onPrepareOptionsMenu(android.view.Menu)",
// Uses android.graphics.Canvas
- "@Deprecated public boolean onCreateThumbnail(android.graphics.Bitmap, android.graphics.Canvas)",
+ "@Deprecated public boolean onCreateThumbnail(android.graphics.Bitmap, android"
+ + ".graphics.Canvas)",
// Uses android.os.CancellationSignal
- "public void onGetDirectActions(@NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<java.util.List<android.app.DirectAction>>)",
- "public void onPerformDirectAction(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.os.Bundle>)",
+ "public void onGetDirectActions(@NonNull android.os.CancellationSignal, @NonNull java"
+ + ".util.function.Consumer<java.util.List<android.app.DirectAction>>)",
+ "public void onPerformDirectAction(@NonNull String, @NonNull android.os.Bundle, "
+ + "@NonNull android.os.CancellationSignal, @NonNull java.util.function"
+ + ".Consumer<android.os.Bundle>)",
// Uses android.view.SearchEvent
"public boolean onSearchRequested(@Nullable android.view.SearchEvent)",
// Uses android.app.Application.ActivityLifecycleCallbacks
- "public void registerActivityLifecycleCallbacks(@NonNull android.app.Application.ActivityLifecycleCallbacks)",
- "public void unregisterActivityLifecycleCallbacks(@NonNull android.app.Application.ActivityLifecycleCallbacks)",
+ "public void registerActivityLifecycleCallbacks(@NonNull android.app.Application"
+ + ".ActivityLifecycleCallbacks)",
+ "public void unregisterActivityLifecycleCallbacks(@NonNull android.app.Application"
+ + ".ActivityLifecycleCallbacks)",
// Uses Runnable
"public final void runOnUiThread(Runnable)",
@@ -471,30 +626,45 @@
"public abstract Object getSystemService(@NonNull String)",
// ContextThemeWrapper
- "protected void onApplyThemeResource(android.content.res.Resources.Theme, int, boolean)",
+ "protected void onApplyThemeResource(android.content.res.Resources.Theme, int, "
+ + "boolean)",
// Context
// Uses java.util.concurrent.Executor
- "public boolean bindIsolatedService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection)",
- "public boolean bindService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection)",
+ "public boolean bindIsolatedService(@NonNull @RequiresPermission android.content"
+ + ".Intent, int, @NonNull String, @NonNull java.util.concurrent.Executor, "
+ + "@NonNull android.content.ServiceConnection)",
+ "public boolean bindService(@NonNull @RequiresPermission android.content.Intent, int,"
+ + " @NonNull java.util.concurrent.Executor, @NonNull android.content"
+ + ".ServiceConnection)",
// Uses android.content.ServiceConnection
- "public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int)",
- "public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle)",
+ "public abstract boolean bindService(@RequiresPermission android.content.Intent, "
+ + "@NonNull android.content.ServiceConnection, int)",
+ "public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content"
+ + ".Intent, @NonNull android.content.ServiceConnection, int, @NonNull android"
+ + ".os.UserHandle)",
"public abstract void unbindService(@NonNull android.content.ServiceConnection)",
"public void updateServiceGroup(@NonNull android.content.ServiceConnection, int, int)",
// Uses android.content.Context
"@NonNull public android.content.Context createAttributionContext(@Nullable String)",
- "public abstract android.content.Context createConfigurationContext(@NonNull android.content.res.Configuration)",
- "@NonNull public android.content.Context createContext(@NonNull android.content.ContextParams)",
- "public abstract android.content.Context createContextForSplit(String) throws android.content.pm.PackageManager.NameNotFoundException",
+ "public abstract android.content.Context createConfigurationContext(@NonNull android"
+ + ".content.res.Configuration)",
+ "@NonNull public android.content.Context createContext(@NonNull android.content"
+ + ".ContextParams)",
+ "public abstract android.content.Context createContextForSplit(String) throws android"
+ + ".content.pm.PackageManager.NameNotFoundException",
"public abstract android.content.Context createDeviceProtectedStorageContext()",
- "@DisplayContext public abstract android.content.Context createDisplayContext(@NonNull android.view.Display)",
- "public abstract android.content.Context createPackageContext(String, int) throws android.content.pm.PackageManager.NameNotFoundException",
- "@NonNull @UiContext public android.content.Context createWindowContext(int, @Nullable android.os.Bundle)",
- "@NonNull @UiContext public android.content.Context createWindowContext(@NonNull android.view.Display, int, @Nullable android.os.Bundle)",
+ "@DisplayContext public abstract android.content.Context createDisplayContext"
+ + "(@NonNull android.view.Display)",
+ "public abstract android.content.Context createPackageContext(String, int) throws "
+ + "android.content.pm.PackageManager.NameNotFoundException",
+ "@NonNull @UiContext public android.content.Context createWindowContext(int, "
+ + "@Nullable android.os.Bundle)",
+ "@NonNull @UiContext public android.content.Context createWindowContext(@NonNull "
+ + "android.view.Display, int, @Nullable android.os.Bundle)",
"public abstract android.content.Context getApplicationContext()",
// Uses android.content.res.AssetManager
@@ -507,7 +677,8 @@
"@Nullable public android.view.Display getDisplay()",
// Uses android.graphics.drawable.Drawable
- "@Nullable public final android.graphics.drawable.Drawable getDrawable(@DrawableRes int)",
+ "@Nullable public final android.graphics.drawable.Drawable getDrawable(@DrawableRes "
+ + "int)",
"@Deprecated public abstract android.graphics.drawable.Drawable getWallpaper()",
"@Deprecated public abstract android.graphics.drawable.Drawable peekWallpaper()",
@@ -533,39 +704,70 @@
"public abstract android.content.res.Resources.Theme getTheme()",
// Uses android.content.res.TypedArray
- "@NonNull public final android.content.res.TypedArray obtainStyledAttributes(@NonNull @StyleableRes int[])",
- "@NonNull public final android.content.res.TypedArray obtainStyledAttributes(@StyleRes int, @NonNull @StyleableRes int[]) throws android.content.res.Resources.NotFoundException",
- "@NonNull public final android.content.res.TypedArray obtainStyledAttributes(@Nullable android.util.AttributeSet, @NonNull @StyleableRes int[])",
- "@NonNull public final android.content.res.TypedArray obtainStyledAttributes(@Nullable android.util.AttributeSet, @NonNull @StyleableRes int[], @AttrRes int, @StyleRes int)",
+ "@NonNull public final android.content.res.TypedArray obtainStyledAttributes(@NonNull"
+ + " @StyleableRes int[])",
+ "@NonNull public final android.content.res.TypedArray obtainStyledAttributes"
+ + "(@StyleRes int, @NonNull @StyleableRes int[]) throws android.content.res"
+ + ".Resources.NotFoundException",
+ "@NonNull public final android.content.res.TypedArray obtainStyledAttributes"
+ + "(@Nullable android.util.AttributeSet, @NonNull @StyleableRes int[])",
+ "@NonNull public final android.content.res.TypedArray obtainStyledAttributes"
+ + "(@Nullable android.util.AttributeSet, @NonNull @StyleableRes int[], "
+ + "@AttrRes int, @StyleRes int)",
// Uses java.io.FileInputStream
- "public abstract java.io.FileInputStream openFileInput(String) throws java.io.FileNotFoundException",
+ "public abstract java.io.FileInputStream openFileInput(String) throws java.io"
+ + ".FileNotFoundException",
// Uses java.io.FileOutputStream
- "public abstract java.io.FileOutputStream openFileOutput(String, int) throws java.io.FileNotFoundException",
+ "public abstract java.io.FileOutputStream openFileOutput(String, int) throws java.io"
+ + ".FileNotFoundException",
// Uses android.database.sqlite.SQLiteDatabase
- "public abstract android.database.sqlite.SQLiteDatabase openOrCreateDatabase(String, int, android.database.sqlite.SQLiteDatabase.CursorFactory)",
- "public abstract android.database.sqlite.SQLiteDatabase openOrCreateDatabase(String, int, android.database.sqlite.SQLiteDatabase.CursorFactory, @Nullable android.database.DatabaseErrorHandler)",
+ "public abstract android.database.sqlite.SQLiteDatabase openOrCreateDatabase(String, "
+ + "int, android.database.sqlite.SQLiteDatabase.CursorFactory)",
+ "public abstract android.database.sqlite.SQLiteDatabase openOrCreateDatabase(String, "
+ + "int, android.database.sqlite.SQLiteDatabase.CursorFactory, @Nullable "
+ + "android.database.DatabaseErrorHandler)",
// Uses android.content.ComponentCallbacks
"public void registerComponentCallbacks(android.content.ComponentCallbacks)",
"public void unregisterComponentCallbacks(android.content.ComponentCallbacks)",
// Uses android.content.BroadcastReceiver
- "@Nullable public abstract android.content.Intent registerReceiver(@Nullable android.content.BroadcastReceiver, android.content.IntentFilter)",
- "@Nullable public abstract android.content.Intent registerReceiver(@Nullable android.content.BroadcastReceiver, android.content.IntentFilter, int)",
- "@Nullable public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler)",
- "@Nullable public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler, int)",
- "public abstract void sendOrderedBroadcast(@NonNull @RequiresPermission android.content.Intent, @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle)",
- "public void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle)",
- "public abstract void sendOrderedBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle)",
- "public abstract void sendStickyOrderedBroadcast(@RequiresPermission android.content.Intent, android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle)",
- "public abstract void sendStickyOrderedBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle)",
+ "@Nullable public abstract android.content.Intent registerReceiver(@Nullable android"
+ + ".content.BroadcastReceiver, android.content.IntentFilter)",
+ "@Nullable public abstract android.content.Intent registerReceiver(@Nullable android"
+ + ".content.BroadcastReceiver, android.content.IntentFilter, int)",
+ "@Nullable public abstract android.content.Intent registerReceiver(android.content"
+ + ".BroadcastReceiver, android.content.IntentFilter, @Nullable String, "
+ + "@Nullable android.os.Handler)",
+ "@Nullable public abstract android.content.Intent registerReceiver(android.content"
+ + ".BroadcastReceiver, android.content.IntentFilter, @Nullable String, "
+ + "@Nullable android.os.Handler, int)",
+ "public abstract void sendOrderedBroadcast(@NonNull @RequiresPermission android"
+ + ".content.Intent, @Nullable String, @Nullable android.content"
+ + ".BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, "
+ + "@Nullable android.os.Bundle)",
+ "public void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, "
+ + "@Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable "
+ + "android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle)",
+ "public abstract void sendOrderedBroadcastAsUser(@RequiresPermission android.content"
+ + ".Intent, android.os.UserHandle, @Nullable String, android.content"
+ + ".BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, "
+ + "@Nullable android.os.Bundle)",
+ "public abstract void sendStickyOrderedBroadcast(@RequiresPermission android.content"
+ + ".Intent, android.content.BroadcastReceiver, @Nullable android.os.Handler, "
+ + "int, @Nullable String, @Nullable android.os.Bundle)",
+ "public abstract void sendStickyOrderedBroadcastAsUser(@RequiresPermission android"
+ + ".content.Intent, android.os.UserHandle, android.content.BroadcastReceiver,"
+ + " @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os"
+ + ".Bundle)",
"public abstract void unregisterReceiver(android.content.BroadcastReceiver)",
// Uses java.io.InputStream
- "@Deprecated public abstract void setWallpaper(java.io.InputStream) throws java.io.IOException",
+ "@Deprecated public abstract void setWallpaper(java.io.InputStream) throws java.io"
+ + ".IOException",
// Doesn't make sense as it requires an actual Context
@@ -579,58 +781,100 @@
"public static void removeStatusChangeListener(Object)",
// Uses android.content.ContentProviderClient
- "@Nullable public final android.content.ContentProviderClient acquireContentProviderClient(@NonNull android.net.Uri)",
- "@Nullable public final android.content.ContentProviderClient acquireContentProviderClient(@NonNull String)",
- "@Nullable public final android.content.ContentProviderClient acquireUnstableContentProviderClient(@NonNull android.net.Uri)",
- "@Nullable public final android.content.ContentProviderClient acquireUnstableContentProviderClient(@NonNull String)",
+ "@Nullable public final android.content.ContentProviderClient "
+ + "acquireContentProviderClient(@NonNull android.net.Uri)",
+ "@Nullable public final android.content.ContentProviderClient "
+ + "acquireContentProviderClient(@NonNull String)",
+ "@Nullable public final android.content.ContentProviderClient "
+ + "acquireUnstableContentProviderClient(@NonNull android.net.Uri)",
+ "@Nullable public final android.content.ContentProviderClient "
+ + "acquireUnstableContentProviderClient(@NonNull String)",
// Uses android.content.ContentResolver.MimeTypeInfo
- "@NonNull public final android.content.ContentResolver.MimeTypeInfo getTypeInfo(@NonNull String)",
+ "@NonNull public final android.content.ContentResolver.MimeTypeInfo getTypeInfo"
+ + "(@NonNull String)",
// Uses android.util.Size
- "@NonNull public android.graphics.Bitmap loadThumbnail(@NonNull android.net.Uri, @NonNull android.util.Size, @Nullable android.os.CancellationSignal) throws java.io.IOException",
+ "@NonNull public android.graphics.Bitmap loadThumbnail(@NonNull android.net.Uri, "
+ + "@NonNull android.util.Size, @Nullable android.os.CancellationSignal) "
+ + "throws java.io.IOException",
// Uses android.database.ContentObserver
- "public void notifyChange(@NonNull android.net.Uri, @Nullable android.database.ContentObserver)",
- "@Deprecated public void notifyChange(@NonNull android.net.Uri, @Nullable android.database.ContentObserver, boolean)",
- "public void notifyChange(@NonNull android.net.Uri, @Nullable android.database.ContentObserver, int)",
- "public void notifyChange(@NonNull java.util.Collection<android.net.Uri>, @Nullable android.database.ContentObserver, int)",
- "public final void registerContentObserver(@NonNull android.net.Uri, boolean, @NonNull android.database.ContentObserver)",
- "public final void unregisterContentObserver(@NonNull android.database.ContentObserver)",
+ "public void notifyChange(@NonNull android.net.Uri, @Nullable android.database"
+ + ".ContentObserver)",
+ "@Deprecated public void notifyChange(@NonNull android.net.Uri, @Nullable android"
+ + ".database.ContentObserver, boolean)",
+ "public void notifyChange(@NonNull android.net.Uri, @Nullable android.database"
+ + ".ContentObserver, int)",
+ "public void notifyChange(@NonNull java.util.Collection<android.net.Uri>, @Nullable "
+ + "android.database.ContentObserver, int)",
+ "public final void registerContentObserver(@NonNull android.net.Uri, boolean, "
+ + "@NonNull android.database.ContentObserver)",
+ "public final void unregisterContentObserver(@NonNull android.database"
+ + ".ContentObserver)",
// Uses android.os.CancellationSignal
- "@Nullable public final android.content.res.AssetFileDescriptor openAssetFile(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException",
- "@Nullable public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException",
- "@Nullable public final android.os.ParcelFileDescriptor openFile(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException",
- "@Nullable public final android.os.ParcelFileDescriptor openFileDescriptor(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException",
- "@Nullable public final android.content.res.AssetFileDescriptor openTypedAssetFile(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException",
- "@Nullable public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException",
- "public final boolean refresh(@NonNull android.net.Uri, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal)",
+ "@Nullable public final android.content.res.AssetFileDescriptor openAssetFile"
+ + "(@NonNull android.net.Uri, @NonNull String, @Nullable android.os"
+ + ".CancellationSignal) throws java.io.FileNotFoundException",
+ "@Nullable public final android.content.res.AssetFileDescriptor "
+ + "openAssetFileDescriptor(@NonNull android.net.Uri, @NonNull String, "
+ + "@Nullable android.os.CancellationSignal) throws java.io"
+ + ".FileNotFoundException",
+ "@Nullable public final android.os.ParcelFileDescriptor openFile(@NonNull android.net"
+ + ".Uri, @NonNull String, @Nullable android.os.CancellationSignal) throws "
+ + "java.io.FileNotFoundException",
+ "@Nullable public final android.os.ParcelFileDescriptor openFileDescriptor(@NonNull "
+ + "android.net.Uri, @NonNull String, @Nullable android.os.CancellationSignal)"
+ + " throws java.io.FileNotFoundException",
+ "@Nullable public final android.content.res.AssetFileDescriptor openTypedAssetFile"
+ + "(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.Bundle, "
+ + "@Nullable android.os.CancellationSignal) throws java.io"
+ + ".FileNotFoundException",
+ "@Nullable public final android.content.res.AssetFileDescriptor "
+ + "openTypedAssetFileDescriptor(@NonNull android.net.Uri, @NonNull String, "
+ + "@Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) "
+ + "throws java.io.FileNotFoundException",
+ "public final boolean refresh(@NonNull android.net.Uri, @Nullable android.os.Bundle, "
+ + "@Nullable android.os.CancellationSignal)",
// Uses java.io.InputStream
- "@Nullable public final java.io.InputStream openInputStream(@NonNull android.net.Uri) throws java.io.FileNotFoundException",
+ "@Nullable public final java.io.InputStream openInputStream(@NonNull android.net.Uri)"
+ + " throws java.io.FileNotFoundException",
// Uses java.io.OutputStream
- "@Nullable public final java.io.OutputStream openOutputStream(@NonNull android.net.Uri) throws java.io.FileNotFoundException",
- "@Nullable public final java.io.OutputStream openOutputStream(@NonNull android.net.Uri, @NonNull String) throws java.io.FileNotFoundException",
+ "@Nullable public final java.io.OutputStream openOutputStream(@NonNull android.net"
+ + ".Uri) throws java.io.FileNotFoundException",
+ "@Nullable public final java.io.OutputStream openOutputStream(@NonNull android.net"
+ + ".Uri, @NonNull String) throws java.io.FileNotFoundException",
// Uses android.database.Cursor
- "@Nullable public final android.database.Cursor query(@NonNull @RequiresPermission.Read android.net.Uri, @Nullable String[], @Nullable String, @Nullable String[], @Nullable String)",
- "@Nullable public final android.database.Cursor query(@NonNull @RequiresPermission.Read android.net.Uri, @Nullable String[], @Nullable String, @Nullable String[], @Nullable String, @Nullable android.os.CancellationSignal)",
- "@Nullable public final android.database.Cursor query(@NonNull @RequiresPermission.Read android.net.Uri, @Nullable String[], @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal)",
+ "@Nullable public final android.database.Cursor query(@NonNull @RequiresPermission"
+ + ".Read android.net.Uri, @Nullable String[], @Nullable String, @Nullable "
+ + "String[], @Nullable String)",
+ "@Nullable public final android.database.Cursor query(@NonNull @RequiresPermission"
+ + ".Read android.net.Uri, @Nullable String[], @Nullable String, @Nullable "
+ + "String[], @Nullable String, @Nullable android.os.CancellationSignal)",
+ "@Nullable public final android.database.Cursor query(@NonNull @RequiresPermission"
+ + ".Read android.net.Uri, @Nullable String[], @Nullable android.os.Bundle, "
+ + "@Nullable android.os.CancellationSignal)",
// Uses android.content.ContentResolver
- "@NonNull public static android.content.ContentResolver wrap(@NonNull android.content.ContentProvider)",
- "@NonNull public static android.content.ContentResolver wrap(@NonNull android.content.ContentProviderClient)",
+ "@NonNull public static android.content.ContentResolver wrap(@NonNull android.content"
+ + ".ContentProvider)",
+ "@NonNull public static android.content.ContentResolver wrap(@NonNull android.content"
+ + ".ContentProviderClient)",
// KeyChain
// Uses android.app.Activity
- "public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java.security.Principal[], @Nullable String, int, @Nullable String)",
- "public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java.security.Principal[], @Nullable android.net.Uri, @Nullable String)"
-
-
+ "public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull "
+ + "android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java"
+ + ".security.Principal[], @Nullable String, int, @Nullable String)",
+ "public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull "
+ + "android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java"
+ + ".security.Principal[], @Nullable android.net.Uri, @Nullable String)"
);
@@ -641,6 +885,19 @@
private static final ClassName NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME =
ClassName.get("com.android.bedstead.remoteframeworkclasses",
"NullParcelableRemoteContentResolver");
+
+ // TODO(b/205562849): These only support passing null, which is fine for existing tests but
+ // will be misleading
+ private static final ClassName NULL_PARCELABLE_ACTIVITY_CLASSNAME =
+ ClassName.get("com.android.bedstead.remoteframeworkclasses",
+ "NullParcelableActivity");
+ private static final ClassName NULL_PARCELABLE_ACCOUNT_MANAGER_CALLBACK_CLASSNAME =
+ ClassName.get("com.android.bedstead.remoteframeworkclasses",
+ "NullParcelableAccountManagerCallback");
+ private static final ClassName NULL_HANDLER_CALLBACK_CLASSNAME =
+ ClassName.get("com.android.bedstead.remoteframeworkclasses",
+ "NullParcelableHandler");
+
private static final ClassName COMPONENT_NAME_CLASSNAME =
ClassName.get("android.content", "ComponentName");
@@ -660,6 +917,7 @@
Set<MethodSignature> blocklistedMethodSignatures = BLOCKLISTED_METHODS.stream()
.map(m -> MethodSignature.forApiString(
m, processingEnv.getTypeUtils(), processingEnv.getElementUtils()))
+ .filter(Objects::nonNull)
.collect(Collectors.toSet());
for (String systemService : FRAMEWORK_CLASSES) {
@@ -678,6 +936,9 @@
private void generateWrappers() {
generateWrapper(NULL_PARCELABLE_REMOTE_DEVICE_POLICY_MANAGER_CLASSNAME);
generateWrapper(NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME);
+ generateWrapper(NULL_PARCELABLE_ACTIVITY_CLASSNAME);
+ generateWrapper(NULL_PARCELABLE_ACCOUNT_MANAGER_CALLBACK_CLASSNAME);
+ generateWrapper(NULL_HANDLER_CALLBACK_CLASSNAME);
}
private void generateWrapper(ClassName className) {
@@ -712,7 +973,7 @@
Set<MethodSignature> blocklistedMethodSignatures,
Elements elements) {
Set<ExecutableElement> methods = filterMethods(getMethods(frameworkClass,
- processingEnv.getElementUtils()),
+ processingEnv.getElementUtils()),
Apis.forClass(frameworkClass.getQualifiedName().toString(),
processingEnv.getTypeUtils(), processingEnv.getElementUtils()), elements)
.stream()
@@ -761,9 +1022,13 @@
classBuilder.addAnnotation(AnnotationSpec.builder(CrossUser.class)
- .addMember("parcelableWrappers", "{$T.class, $T.class}",
+ .addMember("parcelableWrappers",
+ "{$T.class, $T.class, $T.class, $T.class, $T.class}",
NULL_PARCELABLE_REMOTE_DEVICE_POLICY_MANAGER_CLASSNAME,
- NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME)
+ NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME,
+ NULL_PARCELABLE_ACTIVITY_CLASSNAME,
+ NULL_PARCELABLE_ACCOUNT_MANAGER_CALLBACK_CLASSNAME,
+ NULL_HANDLER_CALLBACK_CLASSNAME)
.addMember("futureWrappers", "$T.class",
ACCOUNT_MANAGE_FUTURE_WRAPPER_CLASSNAME)
.build());
@@ -815,9 +1080,13 @@
TypeSpec.classBuilder(className).addModifiers(Modifier.FINAL, Modifier.PUBLIC);
classBuilder.addAnnotation(AnnotationSpec.builder(CrossUser.class)
- .addMember("parcelableWrappers", "{$T.class, $T.class}",
+ .addMember("parcelableWrappers",
+ "{$T.class, $T.class, $T.class, $T.class, $T.class}",
NULL_PARCELABLE_REMOTE_DEVICE_POLICY_MANAGER_CLASSNAME,
- NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME)
+ NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME,
+ NULL_PARCELABLE_ACTIVITY_CLASSNAME,
+ NULL_PARCELABLE_ACCOUNT_MANAGER_CALLBACK_CLASSNAME,
+ NULL_HANDLER_CALLBACK_CLASSNAME)
.build());
classBuilder.addField(ClassName.get(frameworkClass),
@@ -909,7 +1178,7 @@
"Remote" + frameworkClass.getSimpleName().toString() + "Impl");
TypeSpec.Builder classBuilder =
TypeSpec.classBuilder(
- className)
+ className)
.addSuperinterface(interfaceClassName)
.addModifiers(Modifier.PUBLIC);
@@ -960,9 +1229,12 @@
if (signatureReturnOverrides.containsKey(signature)) {
methodBuilder.returns(signatureReturnOverrides.get(signature));
methodBuilder.addStatement(
- "return new $TImpl($L.$L($L))",
+ "$TImpl ret = new $TImpl($L.$L($L))",
+ signatureReturnOverrides.get(signature),
signatureReturnOverrides.get(signature), frameworkClassName,
method.getSimpleName(), String.join(", ", paramNames));
+ // We assume all replacements are null-only
+ methodBuilder.addStatement("return null");
} else if (method.getReturnType().getKind().equals(TypeKind.VOID)) {
methodBuilder.addStatement(
"$L.$L($L)",
@@ -1040,6 +1312,6 @@
private String methodHash(ExecutableElement method) {
return method.getSimpleName() + "(" + method.getParameters().stream()
.map(p -> p.asType().toString()).collect(
- Collectors.joining(",")) + ")";
+ Collectors.joining(",")) + ")";
}
}
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/current.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/current.txt
index adabda3..5984cdd 100644
--- a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/current.txt
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/current.txt
@@ -61,6 +61,7 @@
field public static final String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED";
field public static final String BLUETOOTH_SCAN = "android.permission.BLUETOOTH_SCAN";
field public static final String BODY_SENSORS = "android.permission.BODY_SENSORS";
+ field public static final String BODY_SENSORS_BACKGROUND = "android.permission.BODY_SENSORS_BACKGROUND";
field public static final String BROADCAST_PACKAGE_REMOVED = "android.permission.BROADCAST_PACKAGE_REMOVED";
field public static final String BROADCAST_SMS = "android.permission.BROADCAST_SMS";
field public static final String BROADCAST_STICKY = "android.permission.BROADCAST_STICKY";
@@ -79,6 +80,7 @@
field public static final String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES";
field public static final String DELETE_CACHE_FILES = "android.permission.DELETE_CACHE_FILES";
field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
+ field public static final String DELIVER_COMPANION_MESSAGES = "android.permission.DELIVER_COMPANION_MESSAGES";
field public static final String DIAGNOSTIC = "android.permission.DIAGNOSTIC";
field public static final String DISABLE_KEYGUARD = "android.permission.DISABLE_KEYGUARD";
field public static final String DUMP = "android.permission.DUMP";
@@ -99,6 +101,7 @@
field public static final String INTERACT_ACROSS_PROFILES = "android.permission.INTERACT_ACROSS_PROFILES";
field public static final String INTERNET = "android.permission.INTERNET";
field public static final String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES";
+ field public static final String LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK = "android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK";
field public static final String LOADER_USAGE_STATS = "android.permission.LOADER_USAGE_STATS";
field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
@@ -112,19 +115,23 @@
field public static final String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
field public static final String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS";
field public static final String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS";
+ field public static final String NEARBY_WIFI_DEVICES = "android.permission.NEARBY_WIFI_DEVICES";
field public static final String NFC = "android.permission.NFC";
field public static final String NFC_PREFERRED_PAYMENT_INFO = "android.permission.NFC_PREFERRED_PAYMENT_INFO";
field public static final String NFC_TRANSACTION_EVENT = "android.permission.NFC_TRANSACTION_EVENT";
field public static final String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS";
field @Deprecated public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY";
+ field public static final String POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS";
field @Deprecated public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
field public static final String QUERY_ALL_PACKAGES = "android.permission.QUERY_ALL_PACKAGES";
+ field public static final String READ_BASIC_PHONE_STATE = "android.permission.READ_BASIC_PHONE_STATE";
field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
field public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
field public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";
field public static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE";
field @Deprecated public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE";
field public static final String READ_LOGS = "android.permission.READ_LOGS";
+ field public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY";
field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
field public static final String READ_PRECISE_PHONE_STATE = "android.permission.READ_PRECISE_PHONE_STATE";
@@ -165,6 +172,7 @@
field public static final String SIGNAL_PERSISTENT_PROCESSES = "android.permission.SIGNAL_PERSISTENT_PROCESSES";
field @Deprecated public static final String SMS_FINANCIAL_TRANSACTIONS = "android.permission.SMS_FINANCIAL_TRANSACTIONS";
field public static final String START_FOREGROUND_SERVICES_FROM_BACKGROUND = "android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND";
+ field public static final String START_VIEW_APP_FEATURES = "android.permission.START_VIEW_APP_FEATURES";
field public static final String START_VIEW_PERMISSION_USAGE = "android.permission.START_VIEW_PERMISSION_USAGE";
field public static final String STATUS_BAR = "android.permission.STATUS_BAR";
field public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
@@ -202,6 +210,7 @@
field public static final String LOCATION = "android.permission-group.LOCATION";
field public static final String MICROPHONE = "android.permission-group.MICROPHONE";
field public static final String NEARBY_DEVICES = "android.permission-group.NEARBY_DEVICES";
+ field public static final String NOTIFICATIONS = "android.permission-group.NOTIFICATIONS";
field public static final String PHONE = "android.permission-group.PHONE";
field public static final String SENSORS = "android.permission-group.SENSORS";
field public static final String SMS = "android.permission-group.SMS";
@@ -405,6 +414,7 @@
field public static final int calendarViewShown = 16843596; // 0x101034c
field public static final int calendarViewStyle = 16843613; // 0x101035d
field public static final int canControlMagnification = 16844039; // 0x1010507
+ field public static final int canDisplayOnRemoteDevices;
field public static final int canPauseRecording = 16844314; // 0x101061a
field public static final int canPerformGestures = 16844045; // 0x101050d
field public static final int canRecord = 16844060; // 0x101051c
@@ -841,7 +851,7 @@
field @Deprecated public static final int isModifier = 16843334; // 0x1010246
field @Deprecated public static final int isRepeatable = 16843336; // 0x1010248
field public static final int isScrollContainer = 16843342; // 0x101024e
- field public static final int isSplitRequired = 16844177; // 0x1010591
+ field @Deprecated public static final int isSplitRequired = 16844177; // 0x1010591
field public static final int isStatic = 16844122; // 0x101055a
field @Deprecated public static final int isSticky = 16843335; // 0x1010247
field public static final int isolatedProcess = 16843689; // 0x10103a9
@@ -1194,8 +1204,10 @@
field public static final int requiredFeature = 16844116; // 0x1010554
field public static final int requiredForAllUsers = 16843728; // 0x10103d0
field public static final int requiredNotFeature = 16844117; // 0x1010555
+ field public static final int requiredSplitTypes;
field public static final int requiresFadingEdge = 16843685; // 0x10103a5
field public static final int requiresSmallestWidthDp = 16843620; // 0x1010364
+ field public static final int resetEnabledSettingsOnAppDataCleared;
field public static final int resizeClip = 16843983; // 0x10104cf
field public static final int resizeMode = 16843619; // 0x1010363
field public static final int resizeable = 16843405; // 0x101028d
@@ -1291,12 +1303,14 @@
field public static final int shareInterpolator = 16843195; // 0x10101bb
field @Deprecated public static final int sharedUserId = 16842763; // 0x101000b
field @Deprecated public static final int sharedUserLabel = 16843361; // 0x1010261
+ field public static final int sharedUserMaxSdkVersion;
field public static final int shell = 16844180; // 0x1010594
field public static final int shortcutDisabledMessage = 16844075; // 0x101052b
field public static final int shortcutId = 16844072; // 0x1010528
field public static final int shortcutLongLabel = 16844074; // 0x101052a
field public static final int shortcutShortLabel = 16844073; // 0x1010529
field public static final int shouldDisableView = 16843246; // 0x10101ee
+ field public static final int shouldUseDefaultUnfoldTransition = 16844364; // 0x101064c
field public static final int showAsAction = 16843481; // 0x10102d9
field public static final int showDefault = 16843258; // 0x10101fa
field public static final int showDividers = 16843561; // 0x1010329
@@ -1328,6 +1342,7 @@
field public static final int splitMotionEvents = 16843503; // 0x10102ef
field public static final int splitName = 16844105; // 0x1010549
field public static final int splitTrack = 16843852; // 0x101044c
+ field public static final int splitTypes;
field public static final int spotShadowAlpha = 16843967; // 0x10104bf
field public static final int src = 16843033; // 0x1010119
field public static final int ssp = 16843747; // 0x10103e3
@@ -1398,6 +1413,7 @@
field public static final int summaryColumn = 16843426; // 0x10102a2
field public static final int summaryOff = 16843248; // 0x10101f0
field public static final int summaryOn = 16843247; // 0x10101ef
+ field public static final int supportedTypes;
field public static final int supportsAssist = 16844016; // 0x10104f0
field public static final int supportsInlineSuggestions = 16844301; // 0x101060d
field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
@@ -1405,6 +1421,7 @@
field public static final int supportsMultipleDisplays = 16844182; // 0x1010596
field public static final int supportsPictureInPicture = 16844023; // 0x10104f7
field public static final int supportsRtl = 16843695; // 0x10103af
+ field public static final int supportsStylusHandwriting;
field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb
field public static final int supportsUploading = 16843419; // 0x101029b
field public static final int suppressesSpellChecker = 16844355; // 0x1010643
@@ -2012,6 +2029,9 @@
public static final class R.id {
ctor public R.id();
field public static final int accessibilityActionContextClick = 16908348; // 0x102003c
+ field public static final int accessibilityActionDragCancel = 16908375; // 0x1020057
+ field public static final int accessibilityActionDragDrop = 16908374; // 0x1020056
+ field public static final int accessibilityActionDragStart = 16908373; // 0x1020055
field public static final int accessibilityActionHideTooltip = 16908357; // 0x1020045
field public static final int accessibilityActionImeEnter = 16908372; // 0x1020054
field public static final int accessibilityActionMoveWindow = 16908354; // 0x1020042
@@ -2027,7 +2047,12 @@
field public static final int accessibilityActionScrollUp = 16908344; // 0x1020038
field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d
field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036
+ field public static final int accessibilityActionShowSuggestions;
field public static final int accessibilityActionShowTooltip = 16908356; // 0x1020044
+ field public static final int accessibilityActionSwipeDown;
+ field public static final int accessibilityActionSwipeLeft;
+ field public static final int accessibilityActionSwipeRight;
+ field public static final int accessibilityActionSwipeUp;
field public static final int accessibilitySystemActionBack = 16908363; // 0x102004b
field public static final int accessibilitySystemActionHome = 16908364; // 0x102004c
field public static final int accessibilitySystemActionLockScreen = 16908370; // 0x1020052
@@ -2977,6 +3002,7 @@
}
public final class AccessibilityGestureEvent implements android.os.Parcelable {
+ ctor public AccessibilityGestureEvent(int, int, @NonNull java.util.List<android.view.MotionEvent>);
method public int describeContents();
method @NonNull public static String gestureIdToString(int);
method public int getDisplayId();
@@ -2988,6 +3014,8 @@
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
+ method public boolean clearCache();
+ method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo);
method public final void disableSelf();
method public final boolean dispatchGesture(@NonNull android.accessibilityservice.GestureDescription, @Nullable android.accessibilityservice.AccessibilityService.GestureResultCallback, @Nullable android.os.Handler);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
@@ -2999,8 +3027,11 @@
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
method @NonNull public final android.accessibilityservice.AccessibilityService.SoftKeyboardController getSoftKeyboardController();
method @NonNull public final java.util.List<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> getSystemActions();
+ method @NonNull public final android.accessibilityservice.TouchInteractionController getTouchInteractionController(int);
method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
method @NonNull public final android.util.SparseArray<java.util.List<android.view.accessibility.AccessibilityWindowInfo>> getWindowsOnAllDisplays();
+ method public boolean isCacheEnabled();
+ method public boolean isNodeInCache(@NonNull android.view.accessibility.AccessibilityNodeInfo);
method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public final android.os.IBinder onBind(android.content.Intent);
method @Deprecated protected boolean onGesture(int);
@@ -3011,6 +3042,7 @@
method public void onSystemActionsChanged();
method public final boolean performGlobalAction(int);
method public void setAccessibilityFocusAppearance(int, @ColorInt int);
+ method public boolean setCacheEnabled(boolean);
method public void setGestureDetectionPassthroughRegion(int, @NonNull android.graphics.Region);
method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
method public void setTouchExplorationPassthroughRegion(int, @NonNull android.graphics.Region);
@@ -3098,11 +3130,13 @@
method public void addListener(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, @Nullable android.os.Handler);
method public float getCenterX();
method public float getCenterY();
+ method @Nullable public android.accessibilityservice.MagnificationConfig getMagnificationConfig();
method @NonNull public android.graphics.Region getMagnificationRegion();
method public float getScale();
method public boolean removeListener(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
method public boolean reset(boolean);
method public boolean setCenter(float, float, boolean);
+ method public boolean setMagnificationConfig(@NonNull android.accessibilityservice.MagnificationConfig, boolean);
method public boolean setScale(float, boolean);
}
@@ -3121,8 +3155,12 @@
method public void addOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener, @Nullable android.os.Handler);
method public int getShowMode();
method public boolean removeOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
+ method @CheckResult public int setInputMethodEnabled(@NonNull String, boolean) throws java.lang.SecurityException;
method public boolean setShowMode(int);
method public boolean switchToInputMethod(@NonNull String);
+ field public static final int ENABLE_IME_FAIL_BY_ADMIN = 1; // 0x1
+ field public static final int ENABLE_IME_FAIL_UNKNOWN = 2; // 0x2
+ field public static final int ENABLE_IME_SUCCESS = 0; // 0x0
}
public static interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener {
@@ -3233,6 +3271,53 @@
method public boolean willContinue();
}
+ public final class MagnificationConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public float getCenterX();
+ method public float getCenterY();
+ method public int getMode();
+ method public float getScale();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.MagnificationConfig> CREATOR;
+ field public static final int MAGNIFICATION_MODE_DEFAULT = 0; // 0x0
+ field public static final int MAGNIFICATION_MODE_FULLSCREEN = 1; // 0x1
+ field public static final int MAGNIFICATION_MODE_WINDOW = 2; // 0x2
+ }
+
+ public static final class MagnificationConfig.Builder {
+ ctor public MagnificationConfig.Builder();
+ method @NonNull public android.accessibilityservice.MagnificationConfig build();
+ method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterX(float);
+ method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterY(float);
+ method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setMode(int);
+ method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setScale(@FloatRange(from=1.0f, to=8.0f) float);
+ }
+
+ public final class TouchInteractionController {
+ method public int getDisplayId();
+ method public int getMaxPointerCount();
+ method public int getState();
+ method public void performClick();
+ method public void performLongClickAndStartDrag();
+ method public void registerCallback(@Nullable java.util.concurrent.Executor, @NonNull android.accessibilityservice.TouchInteractionController.Callback);
+ method public void requestDelegating();
+ method public void requestDragging(int);
+ method public void requestTouchExploration();
+ method @NonNull public static String stateToString(int);
+ method public void unregisterAllCallbacks();
+ method public boolean unregisterCallback(@NonNull android.accessibilityservice.TouchInteractionController.Callback);
+ field public static final int STATE_CLEAR = 0; // 0x0
+ field public static final int STATE_DELEGATING = 4; // 0x4
+ field public static final int STATE_DRAGGING = 3; // 0x3
+ field public static final int STATE_TOUCH_EXPLORING = 2; // 0x2
+ field public static final int STATE_TOUCH_INTERACTING = 1; // 0x1
+ }
+
+ public static interface TouchInteractionController.Callback {
+ method public void onMotionEvent(@NonNull android.view.MotionEvent);
+ method public void onStateChanged(int);
+ }
+
}
package android.accounts {
@@ -3918,6 +4003,7 @@
method public void dump(@NonNull String, @Nullable java.io.FileDescriptor, @NonNull java.io.PrintWriter, @Nullable String[]);
method @Deprecated public void enterPictureInPictureMode();
method public boolean enterPictureInPictureMode(@NonNull android.app.PictureInPictureParams);
+ method public <T extends android.view.View> T findViewById(@IdRes int);
method public void finish();
method public void finishActivity(int);
method @Deprecated public void finishActivityFromChild(@NonNull android.app.Activity, int);
@@ -4081,6 +4167,7 @@
method public final void requestShowKeyboardShortcuts();
method @Deprecated public boolean requestVisibleBehind(boolean);
method public final boolean requestWindowFeature(int);
+ method @NonNull public final <T extends android.view.View> T requireViewById(@IdRes int);
method public final void runOnUiThread(Runnable);
method public void setActionBar(@Nullable android.widget.Toolbar);
method public void setContentTransitionManager(android.transition.TransitionManager);
@@ -4367,6 +4454,7 @@
method @Nullable public android.graphics.Rect getLaunchBounds();
method public int getLaunchDisplayId();
method public boolean getLockTaskMode();
+ method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
method public static android.app.ActivityOptions makeBasic();
method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
@@ -4380,6 +4468,8 @@
method public android.app.ActivityOptions setLaunchBounds(@Nullable android.graphics.Rect);
method public android.app.ActivityOptions setLaunchDisplayId(int);
method public android.app.ActivityOptions setLockTaskEnabled(boolean);
+ method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
+ method @NonNull public android.app.ActivityOptions setSplashScreenStyle(int);
method public android.os.Bundle toBundle();
method public void update(android.app.ActivityOptions);
field public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
@@ -5365,12 +5455,30 @@
public final class GameManager {
method public int getGameMode();
+ method public void setGameState(@NonNull android.app.GameState);
field public static final int GAME_MODE_BATTERY = 3; // 0x3
field public static final int GAME_MODE_PERFORMANCE = 2; // 0x2
field public static final int GAME_MODE_STANDARD = 1; // 0x1
field public static final int GAME_MODE_UNSUPPORTED = 0; // 0x0
}
+ public final class GameState implements android.os.Parcelable {
+ ctor public GameState(boolean, int);
+ ctor public GameState(boolean, int, @Nullable String, @NonNull android.os.Bundle);
+ method public int describeContents();
+ method @Nullable public String getDescription();
+ method @NonNull public android.os.Bundle getMetadata();
+ method public int getMode();
+ method public boolean isLoading();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.GameState> CREATOR;
+ field public static final int MODE_CONTENT = 4; // 0x4
+ field public static final int MODE_GAMEPLAY_INTERRUPTIBLE = 2; // 0x2
+ field public static final int MODE_GAMEPLAY_UNINTERRUPTIBLE = 3; // 0x3
+ field public static final int MODE_NONE = 1; // 0x1
+ field public static final int MODE_UNKNOWN = 0; // 0x0
+ }
+
public class Instrumentation {
ctor public Instrumentation();
method public android.os.TestLooperManager acquireLooperManager(android.os.Looper);
@@ -5583,6 +5691,11 @@
method @Deprecated public android.view.Window startActivity(String, android.content.Intent);
}
+ public class LocaleManager {
+ method @NonNull public android.os.LocaleList getApplicationLocales();
+ method public void setApplicationLocales(@NonNull android.os.LocaleList);
+ }
+
public class MediaRouteActionProvider extends android.view.ActionProvider {
ctor public MediaRouteActionProvider(android.content.Context);
method public android.view.View onCreateActionView();
@@ -6168,10 +6281,12 @@
method public long[] getVibrationPattern();
method public boolean hasUserSetImportance();
method public boolean hasUserSetSound();
+ method public boolean isBlockable();
method public boolean isConversation();
method public boolean isDemoted();
method public boolean isImportantConversation();
method public void setAllowBubbles(boolean);
+ method public void setBlockable(boolean);
method public void setBypassDnd(boolean);
method public void setConversationId(@NonNull String, @NonNull String);
method public void setDescription(String);
@@ -6244,6 +6359,7 @@
method public android.app.NotificationManager.Policy getNotificationPolicy();
method public boolean isNotificationListenerAccessGranted(android.content.ComponentName);
method public boolean isNotificationPolicyAccessGranted();
+ method @WorkerThread public boolean matchesCallFilter(@NonNull android.net.Uri);
method public void notify(int, android.app.Notification);
method public void notify(String, int, android.app.Notification);
method public void notifyAsPackage(@NonNull String, @Nullable String, int, @NonNull android.app.Notification);
@@ -6650,7 +6766,7 @@
method public boolean onUnbind(android.content.Intent);
method public final void startForeground(int, android.app.Notification);
method public final void startForeground(int, @NonNull android.app.Notification, int);
- method public final void stopForeground(boolean);
+ method @Deprecated public final void stopForeground(boolean);
method public final void stopForeground(int);
method public final void stopSelf();
method public final void stopSelf(int);
@@ -6663,6 +6779,7 @@
field public static final int START_STICKY = 1; // 0x1
field public static final int START_STICKY_COMPATIBILITY = 0; // 0x0
field public static final int STOP_FOREGROUND_DETACH = 2; // 0x2
+ field @Deprecated public static final int STOP_FOREGROUND_LEGACY = 0; // 0x0
field public static final int STOP_FOREGROUND_REMOVE = 1; // 0x1
}
@@ -6685,6 +6802,16 @@
}
public class StatusBarManager {
+ method public void requestAddTileService(@NonNull android.content.ComponentName, @NonNull CharSequence, @NonNull android.graphics.drawable.Icon, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ field public static final int TILE_ADD_REQUEST_ERROR_APP_NOT_IN_FOREGROUND = 1004; // 0x3ec
+ field public static final int TILE_ADD_REQUEST_ERROR_BAD_COMPONENT = 1002; // 0x3ea
+ field public static final int TILE_ADD_REQUEST_ERROR_MISMATCHED_PACKAGE = 1000; // 0x3e8
+ field public static final int TILE_ADD_REQUEST_ERROR_NOT_CURRENT_USER = 1003; // 0x3eb
+ field public static final int TILE_ADD_REQUEST_ERROR_NO_STATUS_BAR_SERVICE = 1005; // 0x3ed
+ field public static final int TILE_ADD_REQUEST_ERROR_REQUEST_IN_PROGRESS = 1001; // 0x3e9
+ field public static final int TILE_ADD_REQUEST_RESULT_TILE_ADDED = 2; // 0x2
+ field public static final int TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED = 1; // 0x1
+ field public static final int TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED = 0; // 0x0
}
public final class SyncNotedAppOp implements android.os.Parcelable {
@@ -6705,6 +6832,7 @@
}
public class TaskInfo {
+ method public boolean isVisible();
field @Nullable public android.content.ComponentName baseActivity;
field @NonNull public android.content.Intent baseIntent;
field public boolean isRunning;
@@ -6746,7 +6874,7 @@
public final class UiAutomation {
method public void adoptShellPermissionIdentity();
method public void adoptShellPermissionIdentity(@Nullable java.lang.String...);
- method public void clearWindowAnimationFrameStats();
+ method @Deprecated public void clearWindowAnimationFrameStats();
method public boolean clearWindowContentFrameStats(int);
method public void dropShellPermissionIdentity();
method public android.view.accessibility.AccessibilityEvent executeAndWaitForEvent(Runnable, android.app.UiAutomation.AccessibilityEventFilter, long) throws java.util.concurrent.TimeoutException;
@@ -6755,7 +6883,7 @@
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
- method public android.view.WindowAnimationFrameStats getWindowAnimationFrameStats();
+ method @Deprecated public android.view.WindowAnimationFrameStats getWindowAnimationFrameStats();
method public android.view.WindowContentFrameStats getWindowContentFrameStats(int);
method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
method @NonNull public android.util.SparseArray<java.util.List<android.view.accessibility.AccessibilityWindowInfo>> getWindowsOnAllDisplays();
@@ -6920,6 +7048,7 @@
method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
method public CharSequence loadLabel(android.content.pm.PackageManager);
method public android.graphics.drawable.Drawable loadThumbnail(android.content.pm.PackageManager);
+ method public boolean shouldUseDefaultUnfoldTransition();
method public boolean supportsMultipleDisplays();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.WallpaperInfo> CREATOR;
@@ -6978,7 +7107,7 @@
}
public static interface WallpaperManager.OnColorsChangedListener {
- method public void onColorsChanged(android.app.WallpaperColors, int);
+ method public void onColorsChanged(@Nullable android.app.WallpaperColors, int);
}
public interface ZygotePreload {
@@ -7155,8 +7284,8 @@
method public int getMaximumFailedPasswordsForWipe(@Nullable android.content.ComponentName);
method public long getMaximumTimeToLock(@Nullable android.content.ComponentName);
method @NonNull public java.util.List<java.lang.String> getMeteredDataDisabledPackages(@NonNull android.content.ComponentName);
- method public int getNearbyAppStreamingPolicy();
- method public int getNearbyNotificationStreamingPolicy();
+ method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyAppStreamingPolicy();
+ method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyNotificationStreamingPolicy();
method @Deprecated @ColorInt public int getOrganizationColor(@NonNull android.content.ComponentName);
method @Nullable public CharSequence getOrganizationName(@NonNull android.content.ComponentName);
method public java.util.List<android.telephony.data.ApnSetting> getOverrideApns(@NonNull android.content.ComponentName);
@@ -7453,7 +7582,7 @@
field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
field public static final int KEYGUARD_DISABLE_FINGERPRINT = 32; // 0x20
field public static final int KEYGUARD_DISABLE_IRIS = 256; // 0x100
- field public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 64; // 0x40
+ field @Deprecated public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 64; // 0x40
field public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 2; // 0x2
field public static final int KEYGUARD_DISABLE_SECURE_NOTIFICATIONS = 4; // 0x4
field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
@@ -7935,7 +8064,9 @@
method public static final long getMinFlexMillis();
method public long getMinLatencyMillis();
method public static final long getMinPeriodMillis();
+ method public long getMinimumNetworkChunkBytes();
method @Deprecated public int getNetworkType();
+ method public int getPriority();
method @Nullable public android.net.NetworkRequest getRequiredNetwork();
method @NonNull public android.content.ComponentName getService();
method @NonNull public android.os.Bundle getTransientExtras();
@@ -7964,6 +8095,11 @@
field public static final int NETWORK_TYPE_NONE = 0; // 0x0
field public static final int NETWORK_TYPE_NOT_ROAMING = 3; // 0x3
field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
+ field public static final int PRIORITY_DEFAULT = 300; // 0x12c
+ field public static final int PRIORITY_HIGH = 400; // 0x190
+ field public static final int PRIORITY_LOW = 200; // 0xc8
+ field public static final int PRIORITY_MAX = 500; // 0x1f4
+ field public static final int PRIORITY_MIN = 100; // 0x64
}
public static final class JobInfo.Builder {
@@ -7977,11 +8113,13 @@
method public android.app.job.JobInfo.Builder setExtras(@NonNull android.os.PersistableBundle);
method @Deprecated public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean);
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
+ method @NonNull public android.app.job.JobInfo.Builder setMinimumNetworkChunkBytes(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
method public android.app.job.JobInfo.Builder setPeriodic(long);
method public android.app.job.JobInfo.Builder setPeriodic(long, long);
method @RequiresPermission(android.Manifest.permission.RECEIVE_BOOT_COMPLETED) public android.app.job.JobInfo.Builder setPersisted(boolean);
method public android.app.job.JobInfo.Builder setPrefetch(boolean);
+ method @NonNull public android.app.job.JobInfo.Builder setPriority(int);
method public android.app.job.JobInfo.Builder setRequiredNetwork(@Nullable android.net.NetworkRequest);
method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean);
@@ -8029,6 +8167,7 @@
field public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
field public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9; // 0x9
field public static final int STOP_REASON_DEVICE_STATE = 4; // 0x4
+ field public static final int STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED = 15; // 0xf
field public static final int STOP_REASON_PREEMPT = 2; // 0x2
field public static final int STOP_REASON_QUOTA = 10; // 0xa
field public static final int STOP_REASON_SYSTEM_PROCESSING = 14; // 0xe
@@ -8069,11 +8208,13 @@
public final class JobWorkItem implements android.os.Parcelable {
ctor public JobWorkItem(android.content.Intent);
ctor public JobWorkItem(android.content.Intent, long, long);
+ ctor public JobWorkItem(@Nullable android.content.Intent, long, long, long);
method public int describeContents();
method public int getDeliveryCount();
method public long getEstimatedNetworkDownloadBytes();
method public long getEstimatedNetworkUploadBytes();
method public android.content.Intent getIntent();
+ method public long getMinimumNetworkChunkBytes();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.job.JobWorkItem> CREATOR;
}
@@ -8637,6 +8778,7 @@
method public android.bluetooth.le.BluetoothLeScanner getBluetoothLeScanner();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices();
method @Deprecated public static android.bluetooth.BluetoothAdapter getDefaultAdapter();
+ method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public java.time.Duration getDiscoverableTimeout();
method public int getLeMaximumAdvertisingDataLength();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getName();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getProfileConnectionState(int);
@@ -8648,9 +8790,11 @@
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean isDiscovering();
method public boolean isEnabled();
method public boolean isLe2MPhySupported();
+ method public int isLeAudioSupported();
method public boolean isLeCodedPhySupported();
method public boolean isLeExtendedAdvertisingSupported();
method public boolean isLePeriodicAdvertisingSupported();
+ method public int isLePeriodicAdvertisingSyncTransferSenderSupported();
method public boolean isMultipleAdvertisementSupported();
method public boolean isOffloadedFilteringSupported();
method public boolean isOffloadedScanBatchingSupported();
@@ -8929,11 +9073,15 @@
public final class BluetoothClass implements android.os.Parcelable {
method public int describeContents();
+ method public boolean doesClassMatch(int);
method public int getDeviceClass();
method public int getMajorDeviceClass();
method public boolean hasService(int);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothClass> CREATOR;
+ field public static final int PROFILE_A2DP = 1; // 0x1
+ field public static final int PROFILE_HEADSET = 0; // 0x0
+ field public static final int PROFILE_HID = 3; // 0x3
}
public static class BluetoothClass.Device {
@@ -9018,6 +9166,81 @@
field public static final int TELEPHONY = 4194304; // 0x400000
}
+ public final class BluetoothCodecConfig implements android.os.Parcelable {
+ ctor public BluetoothCodecConfig(int);
+ method public int describeContents();
+ method public int getBitsPerSample();
+ method public int getChannelMode();
+ method public int getCodecPriority();
+ method public long getCodecSpecific1();
+ method public long getCodecSpecific2();
+ method public long getCodecSpecific3();
+ method public long getCodecSpecific4();
+ method public int getCodecType();
+ method public static int getMaxCodecType();
+ method public int getSampleRate();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int BITS_PER_SAMPLE_16 = 1; // 0x1
+ field public static final int BITS_PER_SAMPLE_24 = 2; // 0x2
+ field public static final int BITS_PER_SAMPLE_32 = 4; // 0x4
+ field public static final int BITS_PER_SAMPLE_NONE = 0; // 0x0
+ field public static final int CHANNEL_MODE_MONO = 1; // 0x1
+ field public static final int CHANNEL_MODE_NONE = 0; // 0x0
+ field public static final int CHANNEL_MODE_STEREO = 2; // 0x2
+ field public static final int CODEC_PRIORITY_DEFAULT = 0; // 0x0
+ field public static final int CODEC_PRIORITY_DISABLED = -1; // 0xffffffff
+ field public static final int CODEC_PRIORITY_HIGHEST = 1000000; // 0xf4240
+ field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothCodecConfig> CREATOR;
+ field public static final int SAMPLE_RATE_176400 = 16; // 0x10
+ field public static final int SAMPLE_RATE_192000 = 32; // 0x20
+ field public static final int SAMPLE_RATE_44100 = 1; // 0x1
+ field public static final int SAMPLE_RATE_48000 = 2; // 0x2
+ field public static final int SAMPLE_RATE_88200 = 4; // 0x4
+ field public static final int SAMPLE_RATE_96000 = 8; // 0x8
+ field public static final int SAMPLE_RATE_NONE = 0; // 0x0
+ field public static final int SOURCE_CODEC_TYPE_AAC = 1; // 0x1
+ field public static final int SOURCE_CODEC_TYPE_APTX = 2; // 0x2
+ field public static final int SOURCE_CODEC_TYPE_APTX_HD = 3; // 0x3
+ field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240
+ field public static final int SOURCE_CODEC_TYPE_LDAC = 4; // 0x4
+ field public static final int SOURCE_CODEC_TYPE_SBC = 0; // 0x0
+ }
+
+ public static final class BluetoothCodecConfig.Builder {
+ ctor public BluetoothCodecConfig.Builder();
+ method @NonNull public android.bluetooth.BluetoothCodecConfig build();
+ method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setBitsPerSample(int);
+ method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setChannelMode(int);
+ method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecPriority(int);
+ method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific1(long);
+ method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific2(long);
+ method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific3(long);
+ method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific4(long);
+ method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecType(int);
+ method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setSampleRate(int);
+ }
+
+ public final class BluetoothCodecStatus implements android.os.Parcelable {
+ ctor public BluetoothCodecStatus(@Nullable android.bluetooth.BluetoothCodecConfig, @Nullable java.util.List<android.bluetooth.BluetoothCodecConfig>, @Nullable java.util.List<android.bluetooth.BluetoothCodecConfig>);
+ method public int describeContents();
+ method @Nullable public android.bluetooth.BluetoothCodecConfig getCodecConfig();
+ method @NonNull public java.util.List<android.bluetooth.BluetoothCodecConfig> getCodecsLocalCapabilities();
+ method @NonNull public java.util.List<android.bluetooth.BluetoothCodecConfig> getCodecsSelectableCapabilities();
+ method public boolean isCodecConfigSelectable(@Nullable android.bluetooth.BluetoothCodecConfig);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothCodecStatus> CREATOR;
+ field public static final String EXTRA_CODEC_STATUS = "android.bluetooth.extra.CODEC_STATUS";
+ }
+
+ public final class BluetoothCsipSetCoordinator implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
+ method public void close();
+ method protected void finalize();
+ method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
+ method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CSIS_CONNECTION_STATE_CHANGED = "android.bluetooth.action.CSIS_CONNECTION_STATE_CHANGED";
+ }
+
public final class BluetoothDevice implements android.os.Parcelable {
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int);
@@ -9065,6 +9288,7 @@
field public static final String EXTRA_BOND_STATE = "android.bluetooth.device.extra.BOND_STATE";
field public static final String EXTRA_CLASS = "android.bluetooth.device.extra.CLASS";
field public static final String EXTRA_DEVICE = "android.bluetooth.device.extra.DEVICE";
+ field public static final String EXTRA_IS_COORDINATED_SET_MEMBER = "android.bluetooth.extra.IS_COORDINATED_SET_MEMBER";
field public static final String EXTRA_NAME = "android.bluetooth.device.extra.NAME";
field public static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY";
field public static final String EXTRA_PAIRING_VARIANT = "android.bluetooth.device.extra.PAIRING_VARIANT";
@@ -9096,10 +9320,10 @@
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void disconnect();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean discoverServices();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean executeReliableWrite();
- method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
- method public int getConnectionState(android.bluetooth.BluetoothDevice);
+ method @Deprecated public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @Deprecated public int getConnectionState(android.bluetooth.BluetoothDevice);
method public android.bluetooth.BluetoothDevice getDevice();
- method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+ method @Deprecated public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
@@ -9110,14 +9334,17 @@
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean requestMtu(int);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic, boolean);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setPreferredPhy(int, int, int);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean writeDescriptor(android.bluetooth.BluetoothGattDescriptor);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int writeCharacteristic(@NonNull android.bluetooth.BluetoothGattCharacteristic, @NonNull byte[], int);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean writeDescriptor(android.bluetooth.BluetoothGattDescriptor);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int writeDescriptor(@NonNull android.bluetooth.BluetoothGattDescriptor, @NonNull byte[]);
field public static final int CONNECTION_PRIORITY_BALANCED = 0; // 0x0
field public static final int CONNECTION_PRIORITY_HIGH = 1; // 0x1
field public static final int CONNECTION_PRIORITY_LOW_POWER = 2; // 0x2
field public static final int GATT_CONNECTION_CONGESTED = 143; // 0x8f
field public static final int GATT_FAILURE = 257; // 0x101
field public static final int GATT_INSUFFICIENT_AUTHENTICATION = 5; // 0x5
+ field public static final int GATT_INSUFFICIENT_AUTHORIZATION = 8; // 0x8
field public static final int GATT_INSUFFICIENT_ENCRYPTION = 15; // 0xf
field public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 13; // 0xd
field public static final int GATT_INVALID_OFFSET = 7; // 0x7
@@ -9129,11 +9356,14 @@
public abstract class BluetoothGattCallback {
ctor public BluetoothGattCallback();
- method public void onCharacteristicChanged(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic);
- method public void onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int);
+ method @Deprecated public void onCharacteristicChanged(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic);
+ method public void onCharacteristicChanged(@NonNull android.bluetooth.BluetoothGatt, @NonNull android.bluetooth.BluetoothGattCharacteristic, @NonNull byte[]);
+ method @Deprecated public void onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int);
+ method public void onCharacteristicRead(@NonNull android.bluetooth.BluetoothGatt, @NonNull android.bluetooth.BluetoothGattCharacteristic, @NonNull byte[], int);
method public void onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int);
method public void onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int);
- method public void onDescriptorRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int);
+ method @Deprecated public void onDescriptorRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int);
+ method public void onDescriptorRead(@NonNull android.bluetooth.BluetoothGatt, @NonNull android.bluetooth.BluetoothGattDescriptor, int, @NonNull byte[]);
method public void onDescriptorWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int);
method public void onMtuChanged(android.bluetooth.BluetoothGatt, int, int);
method public void onPhyRead(android.bluetooth.BluetoothGatt, int, int, int);
@@ -9150,20 +9380,20 @@
method public int describeContents();
method public android.bluetooth.BluetoothGattDescriptor getDescriptor(java.util.UUID);
method public java.util.List<android.bluetooth.BluetoothGattDescriptor> getDescriptors();
- method public Float getFloatValue(int, int);
+ method @Deprecated public Float getFloatValue(int, int);
method public int getInstanceId();
- method public Integer getIntValue(int, int);
+ method @Deprecated public Integer getIntValue(int, int);
method public int getPermissions();
method public int getProperties();
method public android.bluetooth.BluetoothGattService getService();
- method public String getStringValue(int);
+ method @Deprecated public String getStringValue(int);
method public java.util.UUID getUuid();
- method public byte[] getValue();
+ method @Deprecated public byte[] getValue();
method public int getWriteType();
- method public boolean setValue(byte[]);
- method public boolean setValue(int, int, int);
- method public boolean setValue(int, int, int, int);
- method public boolean setValue(String);
+ method @Deprecated public boolean setValue(byte[]);
+ method @Deprecated public boolean setValue(int, int, int);
+ method @Deprecated public boolean setValue(int, int, int, int);
+ method @Deprecated public boolean setValue(String);
method public void setWriteType(int);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothGattCharacteristic> CREATOR;
@@ -9203,8 +9433,8 @@
method public android.bluetooth.BluetoothGattCharacteristic getCharacteristic();
method public int getPermissions();
method public java.util.UUID getUuid();
- method public byte[] getValue();
- method public boolean setValue(byte[]);
+ method @Deprecated public byte[] getValue();
+ method @Deprecated public boolean setValue(byte[]);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothGattDescriptor> CREATOR;
field public static final byte[] DISABLE_NOTIFICATION_VALUE;
@@ -9231,7 +9461,8 @@
method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int notifyCharacteristicChanged(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothGattCharacteristic, boolean, @NonNull byte[]);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void readPhy(android.bluetooth.BluetoothDevice);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeService(android.bluetooth.BluetoothGattService);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendResponse(android.bluetooth.BluetoothDevice, int, int, int, byte[]);
@@ -9427,9 +9658,24 @@
method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getGroupId(@NonNull android.bluetooth.BluetoothDevice);
field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED";
}
+ public final class BluetoothLeAudioCodecConfig {
+ method @NonNull public String getCodecName();
+ method public int getCodecType();
+ method public static int getMaxCodecType();
+ field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240
+ field public static final int SOURCE_CODEC_TYPE_LC3 = 0; // 0x0
+ }
+
+ public static final class BluetoothLeAudioCodecConfig.Builder {
+ ctor public BluetoothLeAudioCodecConfig.Builder();
+ method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig build();
+ method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setCodecType(int);
+ }
+
public final class BluetoothManager {
method public android.bluetooth.BluetoothAdapter getAdapter();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(int);
@@ -9443,6 +9689,7 @@
method public int getConnectionState(android.bluetooth.BluetoothDevice);
method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
field public static final int A2DP = 2; // 0x2
+ field public static final int CSIP_SET_COORDINATOR = 25; // 0x19
field public static final String EXTRA_PREVIOUS_STATE = "android.bluetooth.profile.extra.PREVIOUS_STATE";
field public static final String EXTRA_STATE = "android.bluetooth.profile.extra.STATE";
field public static final int GATT = 7; // 0x7
@@ -9451,6 +9698,7 @@
field @Deprecated public static final int HEALTH = 3; // 0x3
field public static final int HEARING_AID = 21; // 0x15
field public static final int HID_DEVICE = 19; // 0x13
+ field public static final int LE_AUDIO = 22; // 0x16
field public static final int SAP = 10; // 0xa
field public static final int STATE_CONNECTED = 2; // 0x2
field public static final int STATE_CONNECTING = 1; // 0x1
@@ -9489,7 +9737,12 @@
field public static final int ERROR_BLUETOOTH_NOT_ALLOWED = 2; // 0x2
field public static final int ERROR_BLUETOOTH_NOT_ENABLED = 1; // 0x1
field public static final int ERROR_DEVICE_NOT_BONDED = 3; // 0x3
+ field public static final int ERROR_FEATURE_NOT_SUPPORTED = 10; // 0xa
+ field public static final int ERROR_GATT_WRITE_NOT_ALLOWED = 101; // 0x65
+ field public static final int ERROR_GATT_WRITE_REQUEST_BUSY = 102; // 0x66
field public static final int ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION = 6; // 0x6
+ field public static final int ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION = 8; // 0x8
+ field public static final int ERROR_PROFILE_SERVICE_NOT_BOUND = 9; // 0x9
field public static final int ERROR_UNKNOWN = 2147483647; // 0x7fffffff
field public static final int SUCCESS = 0; // 0x0
}
@@ -9517,6 +9770,7 @@
method public java.util.Map<android.os.ParcelUuid,byte[]> getServiceData();
method @NonNull public java.util.List<android.os.ParcelUuid> getServiceSolicitationUuids();
method public java.util.List<android.os.ParcelUuid> getServiceUuids();
+ method @NonNull public java.util.List<android.bluetooth.le.TransportDiscoveryData> getTransportDiscoveryData();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertiseData> CREATOR;
}
@@ -9527,6 +9781,7 @@
method public android.bluetooth.le.AdvertiseData.Builder addServiceData(android.os.ParcelUuid, byte[]);
method @NonNull public android.bluetooth.le.AdvertiseData.Builder addServiceSolicitationUuid(@NonNull android.os.ParcelUuid);
method public android.bluetooth.le.AdvertiseData.Builder addServiceUuid(android.os.ParcelUuid);
+ method @NonNull public android.bluetooth.le.AdvertiseData.Builder addTransportDiscoveryData(@NonNull android.bluetooth.le.TransportDiscoveryData);
method public android.bluetooth.le.AdvertiseData build();
method public android.bluetooth.le.AdvertiseData.Builder setIncludeDeviceName(boolean);
method public android.bluetooth.le.AdvertiseData.Builder setIncludeTxPowerLevel(boolean);
@@ -9786,12 +10041,50 @@
method public android.bluetooth.le.ScanSettings.Builder setScanMode(int);
}
+ public final class TransportBlock implements android.os.Parcelable {
+ ctor public TransportBlock(int, int, int, @Nullable byte[]);
+ method public int describeContents();
+ method public int getOrgId();
+ method public int getTdsFlags();
+ method @Nullable public byte[] getTransportData();
+ method public int getTransportDataLength();
+ method @Nullable public byte[] toByteArray();
+ method public int totalBytes();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.TransportBlock> CREATOR;
+ }
+
+ public final class TransportDiscoveryData implements android.os.Parcelable {
+ ctor public TransportDiscoveryData(int, @NonNull java.util.List<android.bluetooth.le.TransportBlock>);
+ ctor public TransportDiscoveryData(@NonNull byte[]);
+ method public int describeContents();
+ method @NonNull public java.util.List<android.bluetooth.le.TransportBlock> getTransportBlocks();
+ method public int getTransportDataType();
+ method @Nullable public byte[] toByteArray();
+ method public int totalBytes();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.TransportDiscoveryData> CREATOR;
+ }
+
}
package android.companion {
+ public final class AssociationInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.net.MacAddress getDeviceMacAddress();
+ method @Nullable public String getDeviceProfile();
+ method @Nullable public CharSequence getDisplayName();
+ method public int getId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR;
+ }
+
public final class AssociationRequest implements android.os.Parcelable {
method public int describeContents();
+ method @Nullable public String getDeviceProfile();
+ method @Nullable public CharSequence getDisplayName();
+ method public boolean isSingleDevice();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationRequest> CREATOR;
field public static final String DEVICE_PROFILE_WATCH = "android.app.role.COMPANION_DEVICE_WATCH";
@@ -9802,6 +10095,7 @@
method @NonNull public android.companion.AssociationRequest.Builder addDeviceFilter(@Nullable android.companion.DeviceFilter<?>);
method @NonNull public android.companion.AssociationRequest build();
method @NonNull public android.companion.AssociationRequest.Builder setDeviceProfile(@NonNull String);
+ method @NonNull public android.companion.AssociationRequest.Builder setDisplayName(@NonNull CharSequence);
method @NonNull public android.companion.AssociationRequest.Builder setSingleDevice(boolean);
}
@@ -9837,27 +10131,37 @@
}
public final class CompanionDeviceManager {
- method @RequiresPermission(value=android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull android.companion.CompanionDeviceManager.Callback, @Nullable android.os.Handler);
- method public void disassociate(@NonNull String);
- method @NonNull public java.util.List<java.lang.String> getAssociations();
- method public boolean hasNotificationAccess(android.content.ComponentName);
+ method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING", "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull android.companion.CompanionDeviceManager.Callback, @Nullable android.os.Handler);
+ method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING", "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.Callback);
+ method @Deprecated public void disassociate(@NonNull String);
+ method public void disassociate(int);
+ method @Deprecated @NonNull public java.util.List<java.lang.String> getAssociations();
+ method @NonNull public java.util.List<android.companion.AssociationInfo> getMyAssociations();
+ method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
method public void requestNotificationAccess(android.content.ComponentName);
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
- field public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
+ field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
+ field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
}
public abstract static class CompanionDeviceManager.Callback {
ctor public CompanionDeviceManager.Callback();
- method public abstract void onDeviceFound(android.content.IntentSender);
- method public abstract void onFailure(CharSequence);
+ method public void onAssociationCreated(@NonNull android.companion.AssociationInfo);
+ method public void onAssociationPending(@NonNull android.content.IntentSender);
+ method @Deprecated public void onDeviceFound(@NonNull android.content.IntentSender);
+ method public abstract void onFailure(@Nullable CharSequence);
}
public abstract class CompanionDeviceService extends android.app.Service {
ctor public CompanionDeviceService();
+ method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void dispatchMessage(int, int, @NonNull byte[]);
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
- method @MainThread public abstract void onDeviceAppeared(@NonNull String);
- method @MainThread public abstract void onDeviceDisappeared(@NonNull String);
+ method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String);
+ method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
+ method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
+ method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
+ method @MainThread public void onDispatchMessage(int, int, @NonNull byte[]);
field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
}
@@ -9953,12 +10257,14 @@
method @Nullable public String getPackageName();
method public int getUid();
method public boolean isTrusted(@NonNull android.content.Context);
+ method @NonNull public static android.content.AttributionSource myAttributionSource();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.AttributionSource> CREATOR;
}
public static final class AttributionSource.Builder {
ctor public AttributionSource.Builder(int);
+ ctor public AttributionSource.Builder(@NonNull android.content.AttributionSource);
method @NonNull public android.content.AttributionSource build();
method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
@@ -10533,6 +10839,7 @@
method @NonNull public final String getString(@StringRes int);
method @NonNull public final String getString(@StringRes int, java.lang.Object...);
method public abstract Object getSystemService(@NonNull String);
+ method public final <T> T getSystemService(@NonNull Class<T>);
method @Nullable public abstract String getSystemServiceName(@NonNull Class<?>);
method @NonNull public final CharSequence getText(@StringRes int);
method public abstract android.content.res.Resources.Theme getTheme();
@@ -10648,6 +10955,7 @@
field public static final String KEYGUARD_SERVICE = "keyguard";
field public static final String LAUNCHER_APPS_SERVICE = "launcherapps";
field @UiContext public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
+ field public static final String LOCALE_SERVICE = "locale";
field public static final String LOCATION_SERVICE = "location";
field public static final String MEDIA_COMMUNICATION_SERVICE = "media_communication";
field public static final String MEDIA_METRICS_SERVICE = "media_metrics";
@@ -10670,14 +10978,18 @@
field public static final String PERFORMANCE_HINT_SERVICE = "performance_hint";
field public static final String POWER_SERVICE = "power";
field public static final String PRINT_SERVICE = "print";
+ field public static final int RECEIVER_EXPORTED = 2; // 0x2
+ field public static final int RECEIVER_NOT_EXPORTED = 4; // 0x4
field public static final int RECEIVER_VISIBLE_TO_INSTANT_APPS = 1; // 0x1
field public static final String RESTRICTIONS_SERVICE = "restrictions";
field public static final String ROLE_SERVICE = "role";
field public static final String SEARCH_SERVICE = "search";
field public static final String SENSOR_SERVICE = "sensor";
field public static final String SHORTCUT_SERVICE = "shortcut";
+ field public static final String STATUS_BAR_SERVICE = "statusbar";
field public static final String STORAGE_SERVICE = "storage";
field public static final String STORAGE_STATS_SERVICE = "storagestats";
+ field public static final String SUPPLEMENTAL_PROCESS_SERVICE = "supplemental_process";
field public static final String SYSTEM_HEALTH_SERVICE = "systemhealth";
field public static final String TELECOM_SERVICE = "telecom";
field public static final String TELEPHONY_IMS_SERVICE = "telephony_ims";
@@ -10685,6 +10997,7 @@
field public static final String TELEPHONY_SUBSCRIPTION_SERVICE = "telephony_subscription_service";
field public static final String TEXT_CLASSIFICATION_SERVICE = "textclassification";
field public static final String TEXT_SERVICES_MANAGER_SERVICE = "textservices";
+ field public static final String TV_IAPP_SERVICE = "tv_iapp";
field public static final String TV_INPUT_SERVICE = "tv_input";
field public static final String UI_MODE_SERVICE = "uimode";
field public static final String USAGE_STATS_SERVICE = "usagestats";
@@ -11030,6 +11343,7 @@
field public static final String ACTION_AIRPLANE_MODE_CHANGED = "android.intent.action.AIRPLANE_MODE";
field public static final String ACTION_ALL_APPS = "android.intent.action.ALL_APPS";
field public static final String ACTION_ANSWER = "android.intent.action.ANSWER";
+ field public static final String ACTION_APPLICATION_LOCALE_CHANGED = "android.intent.action.APPLICATION_LOCALE_CHANGED";
field public static final String ACTION_APPLICATION_PREFERENCES = "android.intent.action.APPLICATION_PREFERENCES";
field public static final String ACTION_APPLICATION_RESTRICTIONS_CHANGED = "android.intent.action.APPLICATION_RESTRICTIONS_CHANGED";
field public static final String ACTION_APP_ERROR = "android.intent.action.APP_ERROR";
@@ -11132,6 +11446,7 @@
field public static final String ACTION_QUICK_VIEW = "android.intent.action.QUICK_VIEW";
field public static final String ACTION_REBOOT = "android.intent.action.REBOOT";
field public static final String ACTION_RUN = "android.intent.action.RUN";
+ field public static final String ACTION_SAFETY_CENTER = "android.intent.action.SAFETY_CENTER";
field public static final String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF";
field public static final String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON";
field public static final String ACTION_SEARCH = "android.intent.action.SEARCH";
@@ -11161,6 +11476,7 @@
field public static final String ACTION_VIEW_LOCUS = "android.intent.action.VIEW_LOCUS";
field @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE) public static final String ACTION_VIEW_PERMISSION_USAGE = "android.intent.action.VIEW_PERMISSION_USAGE";
field @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE) public static final String ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD = "android.intent.action.VIEW_PERMISSION_USAGE_FOR_PERIOD";
+ field @RequiresPermission("android.permission.MANAGE_SENSOR_PRIVACY") public static final String ACTION_VIEW_SAFETY_HUB = "android.intent.action.VIEW_SAFETY_HUB";
field public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
field @Deprecated public static final String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
field public static final String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
@@ -11172,11 +11488,13 @@
field public static final String CATEGORY_APP_CONTACTS = "android.intent.category.APP_CONTACTS";
field public static final String CATEGORY_APP_EMAIL = "android.intent.category.APP_EMAIL";
field public static final String CATEGORY_APP_FILES = "android.intent.category.APP_FILES";
+ field public static final String CATEGORY_APP_FITNESS = "android.intent.category.APP_FITNESS";
field public static final String CATEGORY_APP_GALLERY = "android.intent.category.APP_GALLERY";
field public static final String CATEGORY_APP_MAPS = "android.intent.category.APP_MAPS";
field public static final String CATEGORY_APP_MARKET = "android.intent.category.APP_MARKET";
field public static final String CATEGORY_APP_MESSAGING = "android.intent.category.APP_MESSAGING";
field public static final String CATEGORY_APP_MUSIC = "android.intent.category.APP_MUSIC";
+ field public static final String CATEGORY_APP_WEATHER = "android.intent.category.APP_WEATHER";
field public static final String CATEGORY_BROWSABLE = "android.intent.category.BROWSABLE";
field public static final String CATEGORY_CAR_DOCK = "android.intent.category.CAR_DOCK";
field public static final String CATEGORY_CAR_MODE = "android.intent.category.CAR_MODE";
@@ -11248,6 +11566,7 @@
field public static final String EXTRA_INSTALLER_PACKAGE_NAME = "android.intent.extra.INSTALLER_PACKAGE_NAME";
field public static final String EXTRA_INTENT = "android.intent.extra.INTENT";
field public static final String EXTRA_KEY_EVENT = "android.intent.extra.KEY_EVENT";
+ field public static final String EXTRA_LOCALE_LIST = "android.intent.extra.LOCALE_LIST";
field public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY";
field public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID";
field public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES";
@@ -11256,6 +11575,7 @@
field public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
field public static final String EXTRA_PERMISSION_GROUP_NAME = "android.intent.extra.PERMISSION_GROUP_NAME";
field public static final String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
+ field public static final String EXTRA_PREVIOUS_UID = "android.intent.extra.PREVIOUS_UID";
field public static final String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT";
field public static final String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY";
field public static final String EXTRA_QUICK_VIEW_FEATURES = "android.intent.extra.QUICK_VIEW_FEATURES";
@@ -11287,6 +11607,7 @@
field public static final String EXTRA_TIMEZONE = "time-zone";
field public static final String EXTRA_TITLE = "android.intent.extra.TITLE";
field public static final String EXTRA_UID = "android.intent.extra.UID";
+ field public static final String EXTRA_UID_CHANGING = "android.intent.extra.UID_CHANGING";
field public static final String EXTRA_USER = "android.intent.extra.USER";
field public static final String EXTRA_USER_INITIATED = "android.intent.extra.USER_INITIATED";
field public static final int FILL_IN_ACTION = 1; // 0x1
@@ -11369,6 +11690,8 @@
method public final void addDataScheme(String);
method public final void addDataSchemeSpecificPart(String, int);
method public final void addDataType(String) throws android.content.IntentFilter.MalformedMimeTypeException;
+ method @NonNull public java.util.function.Predicate<android.content.Intent> asPredicate();
+ method @NonNull public java.util.function.Predicate<android.content.Intent> asPredicateWithTypeResolution(@NonNull android.content.ContentResolver);
method public final java.util.Iterator<android.content.IntentFilter.AuthorityEntry> authoritiesIterator();
method public final java.util.Iterator<java.lang.String> categoriesIterator();
method public final int countActions();
@@ -12380,6 +12703,7 @@
method @NonNull public java.io.OutputStream openWrite(@NonNull String, long, long) throws java.io.IOException;
method public void removeChildSessionId(int);
method public void removeSplit(@NonNull String) throws java.io.IOException;
+ method public void requestChecksums(@NonNull String, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, java.io.FileNotFoundException;
method @Deprecated public void setChecksums(@NonNull String, @NonNull java.util.List<android.content.pm.Checksum>, @Nullable byte[]) throws java.io.IOException;
method public void setStagingProgress(float);
method public void transfer(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -12501,6 +12825,7 @@
method public abstract boolean addPermissionAsync(@NonNull android.content.pm.PermissionInfo);
method @Deprecated public abstract void addPreferredActivity(@NonNull android.content.IntentFilter, int, @Nullable android.content.ComponentName[], @NonNull android.content.ComponentName);
method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean addWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int);
+ method public boolean canPackageQuery(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract boolean canRequestPackageInstalls();
method public abstract String[] canonicalToCurrentPackageNames(@NonNull String[]);
method @CheckResult public abstract int checkPermission(@NonNull String, @NonNull String);
@@ -12514,7 +12839,8 @@
method @Nullable public abstract android.graphics.drawable.Drawable getActivityBanner(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public abstract android.graphics.drawable.Drawable getActivityIcon(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public abstract android.graphics.drawable.Drawable getActivityIcon(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
- method @NonNull public abstract android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated @NonNull public abstract android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
method @Nullable public abstract android.graphics.drawable.Drawable getActivityLogo(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
method @Nullable public abstract android.graphics.drawable.Drawable getActivityLogo(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public abstract java.util.List<android.content.pm.PermissionGroupInfo> getAllPermissionGroups(int);
@@ -12523,7 +12849,8 @@
method public abstract int getApplicationEnabledSetting(@NonNull String);
method @NonNull public abstract android.graphics.drawable.Drawable getApplicationIcon(@NonNull android.content.pm.ApplicationInfo);
method @NonNull public abstract android.graphics.drawable.Drawable getApplicationIcon(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
- method @NonNull public abstract android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated @NonNull public abstract android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, @NonNull android.content.pm.PackageManager.ApplicationInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public abstract CharSequence getApplicationLabel(@NonNull android.content.pm.ApplicationInfo);
method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull android.content.pm.ApplicationInfo);
method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -12534,27 +12861,36 @@
method @Nullable public abstract android.graphics.drawable.Drawable getDrawable(@NonNull String, @DrawableRes int, @Nullable android.content.pm.ApplicationInfo);
method public void getGroupOfPlatformPermission(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.String>);
method @NonNull public android.content.pm.InstallSourceInfo getInstallSourceInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
- method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
+ method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
+ method @NonNull public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(@NonNull android.content.pm.PackageManager.ApplicationInfoFlags);
method @NonNull public java.util.List<android.content.pm.ModuleInfo> getInstalledModules(int);
- method @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
+ method @Deprecated @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
+ method @NonNull public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(@NonNull android.content.pm.PackageManager.PackageInfoFlags);
method @Deprecated @Nullable public abstract String getInstallerPackageName(@NonNull String);
method @NonNull public abstract byte[] getInstantAppCookie();
method public abstract int getInstantAppCookieMaxBytes();
method @NonNull public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @Nullable public abstract android.content.Intent getLaunchIntentForPackage(@NonNull String);
+ method @NonNull public android.content.IntentSender getLaunchIntentSenderForPackage(@NonNull String);
method @Nullable public abstract android.content.Intent getLeanbackLaunchIntentForPackage(@NonNull String);
method @NonNull public java.util.Set<java.lang.String> getMimeGroup(@NonNull String);
method @NonNull public android.content.pm.ModuleInfo getModuleInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @Nullable public abstract String getNameForUid(int);
- method @Nullable public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, int);
+ method @Deprecated @Nullable public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, int);
+ method @Nullable public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags);
method public abstract int[] getPackageGids(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public abstract int[] getPackageGids(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated public abstract int[] getPackageGids(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Nullable public int[] getPackageGids(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public android.content.pm.PackageInfo getPackageInfo(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public abstract android.content.pm.PackageInstaller getPackageInstaller();
- method public abstract int getPackageUid(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated public abstract int getPackageUid(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public int getPackageUid(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
method @Nullable public abstract String[] getPackagesForUid(int);
- method @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(@NonNull String[], int);
+ method @Deprecated @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(@NonNull String[], int);
+ method @NonNull public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(@NonNull String[], @NonNull android.content.pm.PackageManager.PackageInfoFlags);
method @NonNull public abstract android.content.pm.PermissionGroupInfo getPermissionGroupInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.pm.PermissionInfo getPermissionInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public void getPlatformPermissionsForGroup(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<java.lang.String>>);
@@ -12562,14 +12898,18 @@
method @Deprecated @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int);
method @NonNull public android.content.pm.PackageManager.Property getProperty(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public android.content.pm.PackageManager.Property getProperty(@NonNull String, @NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
- method @NonNull public abstract android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method @NonNull public abstract android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated @NonNull public abstract android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated @NonNull public abstract android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public abstract android.content.res.Resources getResourcesForActivity(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo, @Nullable android.content.res.Configuration) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
- method @NonNull public abstract android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method @NonNull public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
+ method @Deprecated @NonNull public abstract android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated @NonNull public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
+ method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(@NonNull android.content.pm.PackageManager.PackageInfoFlags);
method @Nullable public android.os.Bundle getSuspendedPackageAppExtras();
method public boolean getSyntheticAppDetailsActivityEnabled(@NonNull String);
method @NonNull public abstract android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
@@ -12597,13 +12937,19 @@
method public abstract boolean isSafeMode();
method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryActivityProperty(@NonNull String);
method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryApplicationProperty(@NonNull String);
- method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int);
- method @NonNull public abstract java.util.List<android.content.pm.ProviderInfo> queryContentProviders(@Nullable String, int, int);
+ method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int);
+ method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
+ method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ProviderInfo> queryContentProviders(@Nullable String, int, int);
+ method @NonNull public java.util.List<android.content.pm.ProviderInfo> queryContentProviders(@Nullable String, int, @NonNull android.content.pm.PackageManager.ComponentInfoFlags);
method @NonNull public abstract java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(@NonNull String, int);
- method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull android.content.Intent, int);
- method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable android.content.Intent[], @NonNull android.content.Intent, int);
- method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, int);
- method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, int);
+ method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull android.content.Intent, int);
+ method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
+ method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable android.content.Intent[], @NonNull android.content.Intent, int);
+ method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable java.util.List<android.content.Intent>, @NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
+ method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, int);
+ method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
+ method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, int);
+ method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
method @NonNull public abstract java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(@Nullable String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryProviderProperty(@NonNull String);
method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryReceiverProperty(@NonNull String);
@@ -12612,13 +12958,17 @@
method public abstract void removePermission(@NonNull String);
method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean removeWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int);
method public void requestChecksums(@NonNull String, boolean, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException;
- method @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int);
- method @Nullable public abstract android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, int);
- method @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int);
+ method @Deprecated @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int);
+ method @Nullable public android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
+ method @Deprecated @Nullable public abstract android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, int);
+ method @Nullable public android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, @NonNull android.content.pm.PackageManager.ComponentInfoFlags);
+ method @Deprecated @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int);
+ method @Nullable public android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
method public abstract void setApplicationCategoryHint(@NonNull String, int);
method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(@NonNull String, int, int);
method @RequiresPermission(value="android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS", conditional=true) public boolean setAutoRevokeWhitelisted(@NonNull String, boolean);
method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(@NonNull android.content.ComponentName, int, int);
+ method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setComponentEnabledSettings(@NonNull java.util.List<android.content.pm.PackageManager.ComponentEnabledSetting>);
method public abstract void setInstallerPackageName(@NonNull String, @Nullable String);
method public void setMimeGroup(@NonNull String, @NonNull java.util.Set<java.lang.String>);
method public abstract void updateInstantAppCookie(@Nullable byte[]);
@@ -12657,7 +13007,7 @@
field public static final String FEATURE_CAMERA_LEVEL_FULL = "android.hardware.camera.level.full";
field public static final String FEATURE_CANT_SAVE_STATE = "android.software.cant_save_state";
field public static final String FEATURE_COMPANION_DEVICE_SETUP = "android.software.companion_device_setup";
- field public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
+ field @Deprecated public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
field public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
field public static final String FEATURE_CONTROLS = "android.software.controls";
field public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
@@ -12728,12 +13078,18 @@
field public static final String FEATURE_SIP = "android.software.sip";
field public static final String FEATURE_SIP_VOIP = "android.software.sip.voip";
field public static final String FEATURE_STRONGBOX_KEYSTORE = "android.hardware.strongbox_keystore";
+ field public static final String FEATURE_TELECOM = "android.software.telecom";
field public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+ field public static final String FEATURE_TELEPHONY_CALLING = "android.hardware.telephony.calling";
field public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
+ field public static final String FEATURE_TELEPHONY_DATA = "android.hardware.telephony.data";
field public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
field public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
field public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
+ field public static final String FEATURE_TELEPHONY_MESSAGING = "android.hardware.telephony.messaging";
+ field public static final String FEATURE_TELEPHONY_RADIO_ACCESS = "android.hardware.telephony.radio";
+ field public static final String FEATURE_TELEPHONY_SUBSCRIPTION = "android.hardware.telephony.subscription";
field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television";
field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
@@ -12815,6 +13171,26 @@
field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
}
+ public static final class PackageManager.ApplicationInfoFlags {
+ method public long getValue();
+ method @NonNull public static android.content.pm.PackageManager.ApplicationInfoFlags of(long);
+ }
+
+ public static final class PackageManager.ComponentEnabledSetting implements android.os.Parcelable {
+ ctor public PackageManager.ComponentEnabledSetting(@NonNull android.content.ComponentName, int, int);
+ method public int describeContents();
+ method @Nullable public android.content.ComponentName getComponentName();
+ method public int getEnabledFlags();
+ method public int getEnabledState();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.ComponentEnabledSetting> CREATOR;
+ }
+
+ public static final class PackageManager.ComponentInfoFlags {
+ method public long getValue();
+ method @NonNull public static android.content.pm.PackageManager.ComponentInfoFlags of(long);
+ }
+
public static class PackageManager.NameNotFoundException extends android.util.AndroidException {
ctor public PackageManager.NameNotFoundException();
ctor public PackageManager.NameNotFoundException(String);
@@ -12824,6 +13200,11 @@
method public void onChecksumsReady(@NonNull java.util.List<android.content.pm.ApkChecksum>);
}
+ public static final class PackageManager.PackageInfoFlags {
+ method public long getValue();
+ method @NonNull public static android.content.pm.PackageManager.PackageInfoFlags of(long);
+ }
+
public static final class PackageManager.Property implements android.os.Parcelable {
method public int describeContents();
method public boolean getBoolean();
@@ -12843,6 +13224,11 @@
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.Property> CREATOR;
}
+ public static final class PackageManager.ResolveInfoFlags {
+ method public long getValue();
+ method @NonNull public static android.content.pm.PackageManager.ResolveInfoFlags of(long);
+ }
+
@Deprecated public class PackageStats implements android.os.Parcelable {
ctor @Deprecated public PackageStats(String);
ctor @Deprecated public PackageStats(android.os.Parcel);
@@ -13010,6 +13396,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.SharedLibraryInfo> CREATOR;
field public static final int TYPE_BUILTIN = 0; // 0x0
field public static final int TYPE_DYNAMIC = 1; // 0x1
+ field public static final int TYPE_SDK = 3; // 0x3
field public static final int TYPE_STATIC = 2; // 0x2
field public static final int VERSION_UNDEFINED = -1; // 0xffffffff
}
@@ -13037,6 +13424,7 @@
method public boolean isDynamic();
method public boolean isEnabled();
method public boolean isImmutable();
+ method public boolean isIncludedIn(int);
method public boolean isPinned();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
@@ -13049,6 +13437,7 @@
field public static final int DISABLED_REASON_UNKNOWN = 3; // 0x3
field public static final int DISABLED_REASON_VERSION_LOWER = 100; // 0x64
field public static final String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
+ field public static final int SURFACE_LAUNCHER = 1; // 0x1
}
public static class ShortcutInfo.Builder {
@@ -13057,6 +13446,7 @@
method @NonNull public android.content.pm.ShortcutInfo.Builder setActivity(@NonNull android.content.ComponentName);
method @NonNull public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set<java.lang.String>);
method @NonNull public android.content.pm.ShortcutInfo.Builder setDisabledMessage(@NonNull CharSequence);
+ method @NonNull public android.content.pm.ShortcutInfo.Builder setExcludedFromSurfaces(int);
method @NonNull public android.content.pm.ShortcutInfo.Builder setExtras(@NonNull android.os.PersistableBundle);
method @NonNull public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
method @NonNull public android.content.pm.ShortcutInfo.Builder setIntent(@NonNull android.content.Intent);
@@ -13231,6 +13621,7 @@
method public int describeContents();
method public int diff(android.content.res.Configuration);
method public boolean equals(android.content.res.Configuration);
+ method @NonNull public static android.content.res.Configuration generateDelta(@NonNull android.content.res.Configuration, @NonNull android.content.res.Configuration);
method public int getLayoutDirection();
method @NonNull public android.os.LocaleList getLocales();
method public boolean isLayoutSizeAtLeast(int);
@@ -13384,7 +13775,7 @@
method public float getFloat(@DimenRes int);
method @NonNull public android.graphics.Typeface getFont(@FontRes int) throws android.content.res.Resources.NotFoundException;
method public float getFraction(@FractionRes int, int, int);
- method public int getIdentifier(String, String, String);
+ method @Discouraged(message="Use of this function is discouraged because resource reflection makes it harder to perform build optimizations and compile-time verification of code. It is much more efficient to retrieve resources by identifier (e.g. `R.foo.bar`) than by name (e.g. `getIdentifier(\"bar\", \"foo\", null)`).") public int getIdentifier(String, String, String);
method @NonNull public int[] getIntArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
method public int getInteger(@IntegerRes int) throws android.content.res.Resources.NotFoundException;
method @NonNull public android.content.res.XmlResourceParser getLayout(@LayoutRes int) throws android.content.res.Resources.NotFoundException;
@@ -13404,7 +13795,7 @@
method public CharSequence getText(@StringRes int, CharSequence);
method @NonNull public CharSequence[] getTextArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
method public void getValue(@AnyRes int, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException;
- method public void getValue(String, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException;
+ method @Discouraged(message="Use of this function is discouraged because it makes internal calls to `getIdentifier()`, which uses resource reflection. Reflection makes it harder to perform build optimizations and compile-time verification of code. It is much more efficient to retrieve resource values by identifier (e.g. `getValue(R.foo.bar, outValue, true)`) than by name (e.g. `getValue(\"foo\", outvalue, true)`).") public void getValue(String, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException;
method public void getValueForDensity(@AnyRes int, int, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException;
method @NonNull public android.content.res.XmlResourceParser getXml(@XmlRes int) throws android.content.res.Resources.NotFoundException;
method public final android.content.res.Resources.Theme newTheme();
@@ -14072,11 +14463,21 @@
field public static final int CONFLICT_ROLLBACK = 1; // 0x1
field public static final int CREATE_IF_NECESSARY = 268435456; // 0x10000000
field public static final int ENABLE_WRITE_AHEAD_LOGGING = 536870912; // 0x20000000
+ field public static final String JOURNAL_MODE_DELETE = "DELETE";
+ field public static final String JOURNAL_MODE_MEMORY = "MEMORY";
+ field public static final String JOURNAL_MODE_OFF = "OFF";
+ field public static final String JOURNAL_MODE_PERSIST = "PERSIST";
+ field public static final String JOURNAL_MODE_TRUNCATE = "TRUNCATE";
+ field public static final String JOURNAL_MODE_WAL = "WAL";
field public static final int MAX_SQL_CACHE_SIZE = 100; // 0x64
field public static final int NO_LOCALIZED_COLLATORS = 16; // 0x10
field public static final int OPEN_READONLY = 1; // 0x1
field public static final int OPEN_READWRITE = 0; // 0x0
field public static final int SQLITE_MAX_LIKE_PATTERN_LENGTH = 50000; // 0xc350
+ field public static final String SYNC_MODE_EXTRA = "EXTRA";
+ field public static final String SYNC_MODE_FULL = "FULL";
+ field public static final String SYNC_MODE_NORMAL = "NORMAL";
+ field public static final String SYNC_MODE_OFF = "OFF";
}
public static interface SQLiteDatabase.CursorFactory {
@@ -14956,7 +15357,7 @@
method public void drawTextRun(@NonNull android.graphics.text.MeasuredText, int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint);
method public void drawVertices(@NonNull android.graphics.Canvas.VertexMode, int, @NonNull float[], int, @Nullable float[], int, @Nullable int[], int, @Nullable short[], int, int, @NonNull android.graphics.Paint);
method public void enableZ();
- method public boolean getClipBounds(@Nullable android.graphics.Rect);
+ method public boolean getClipBounds(@NonNull android.graphics.Rect);
method @NonNull public final android.graphics.Rect getClipBounds();
method public int getDensity();
method @Nullable public android.graphics.DrawFilter getDrawFilter();
@@ -15251,9 +15652,11 @@
method public void clearContent();
method @NonNull public android.graphics.HardwareRenderer.FrameRenderRequest createRenderRequest();
method public void destroy();
+ method public static boolean isDrawingEnabled();
method public boolean isOpaque();
method public void notifyFramePending();
method public void setContentRoot(@Nullable android.graphics.RenderNode);
+ method public static void setDrawingEnabled(boolean);
method public void setLightSourceAlpha(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
method public void setLightSourceGeometry(float, float, float, float);
method public void setName(@NonNull String);
@@ -16128,6 +16531,7 @@
method @NonNull public static android.graphics.RenderEffect createColorFilterEffect(@NonNull android.graphics.ColorFilter);
method @NonNull public static android.graphics.RenderEffect createOffsetEffect(float, float);
method @NonNull public static android.graphics.RenderEffect createOffsetEffect(float, float, @NonNull android.graphics.RenderEffect);
+ method @NonNull public static android.graphics.RenderEffect createRuntimeShaderEffect(@NonNull android.graphics.RuntimeShader, @NonNull String);
method @NonNull public static android.graphics.RenderEffect createShaderEffect(@NonNull android.graphics.Shader);
}
@@ -16203,6 +16607,26 @@
method public boolean setUseCompositingLayer(boolean, @Nullable android.graphics.Paint);
}
+ public class RuntimeShader extends android.graphics.Shader {
+ ctor public RuntimeShader(@NonNull String);
+ ctor public RuntimeShader(@NonNull String, boolean);
+ method public boolean isForceOpaque();
+ method public void setColorUniform(@NonNull String, @ColorInt int);
+ method public void setColorUniform(@NonNull String, @ColorLong long);
+ method public void setColorUniform(@NonNull String, @NonNull android.graphics.Color);
+ method public void setFloatUniform(@NonNull String, float);
+ method public void setFloatUniform(@NonNull String, float, float);
+ method public void setFloatUniform(@NonNull String, float, float, float);
+ method public void setFloatUniform(@NonNull String, float, float, float, float);
+ method public void setFloatUniform(@NonNull String, @NonNull float[]);
+ method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader);
+ method public void setIntUniform(@NonNull String, int);
+ method public void setIntUniform(@NonNull String, int, int);
+ method public void setIntUniform(@NonNull String, int, int, int);
+ method public void setIntUniform(@NonNull String, int, int, int, int);
+ method public void setIntUniform(@NonNull String, @NonNull int[]);
+ }
+
public class Shader {
ctor @Deprecated public Shader();
method public boolean getLocalMatrix(@NonNull android.graphics.Matrix);
@@ -16226,6 +16650,7 @@
ctor public SurfaceTexture(boolean);
method public void attachToGLContext(int);
method public void detachFromGLContext();
+ method public long getDataSpace();
method public long getTimestamp();
method public void getTransformMatrix(float[]);
method public boolean isReleased();
@@ -16319,11 +16744,13 @@
public class AdaptiveIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
ctor public AdaptiveIconDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
+ ctor public AdaptiveIconDrawable(@Nullable android.graphics.drawable.Drawable, @Nullable android.graphics.drawable.Drawable, @Nullable android.graphics.drawable.Drawable);
method public void draw(android.graphics.Canvas);
method public android.graphics.drawable.Drawable getBackground();
method public static float getExtraInsetFraction();
method public android.graphics.drawable.Drawable getForeground();
method public android.graphics.Path getIconMask();
+ method @Nullable public android.graphics.drawable.Drawable getMonochrome();
method public int getOpacity();
method public void invalidateDrawable(@NonNull android.graphics.drawable.Drawable);
method public void scheduleDrawable(@NonNull android.graphics.drawable.Drawable, @NonNull Runnable, long);
@@ -17133,8 +17560,12 @@
method @NonNull public android.graphics.text.MeasuredText.Builder appendReplacementRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, @FloatRange(from=0) @Px float);
method @NonNull public android.graphics.text.MeasuredText.Builder appendStyleRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, boolean);
method @NonNull public android.graphics.text.MeasuredText build();
- method @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(boolean);
+ method @Deprecated @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(boolean);
+ method @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(int);
method @NonNull public android.graphics.text.MeasuredText.Builder setComputeLayout(boolean);
+ field public static final int HYPHENATION_MODE_FAST = 2; // 0x2
+ field public static final int HYPHENATION_MODE_NONE = 0; // 0x0
+ field public static final int HYPHENATION_MODE_NORMAL = 1; // 0x1
}
public final class PositionedGlyphs {
@@ -17437,6 +17868,52 @@
method public int getMinFrequency();
}
+ public final class DataSpace {
+ method public static long getRange(long);
+ method public static long getStandard(long);
+ method public static long getTransfer(long);
+ method public static long pack(long, long, long);
+ field public static final long DATASPACE_ADOBE_RGB = 151715840L; // 0x90b0000L
+ field public static final long DATASPACE_BT2020 = 147193856L; // 0x8c60000L
+ field public static final long DATASPACE_BT2020_PQ = 163971072L; // 0x9c60000L
+ field public static final long DATASPACE_BT601_525 = 281280512L; // 0x10c40000L
+ field public static final long DATASPACE_BT601_625 = 281149440L; // 0x10c20000L
+ field public static final long DATASPACE_BT709 = 281083904L; // 0x10c10000L
+ field public static final long DATASPACE_DCI_P3 = 155844608L; // 0x94a0000L
+ field public static final long DATASPACE_DISPLAY_P3 = 143261696L; // 0x88a0000L
+ field public static final long DATASPACE_JFIF = 146931712L; // 0x8c20000L
+ field public static final long DATASPACE_SCRGB = 411107328L; // 0x18810000L
+ field public static final long DATASPACE_SCRGB_LINEAR = 406913024L; // 0x18410000L
+ field public static final long DATASPACE_SRGB = 142671872L; // 0x8810000L
+ field public static final long DATASPACE_SRGB_LINEAR = 138477568L; // 0x8410000L
+ field public static final long DATASPACE_UNKNOWN = 0L; // 0x0L
+ field public static final long RANGE_EXTENDED = 402653184L; // 0x18000000L
+ field public static final long RANGE_FULL = 134217728L; // 0x8000000L
+ field public static final long RANGE_LIMITED = 268435456L; // 0x10000000L
+ field public static final long RANGE_UNSPECIFIED = 0L; // 0x0L
+ field public static final long STANDARD_ADOBE_RGB = 720896L; // 0xb0000L
+ field public static final long STANDARD_BT2020 = 393216L; // 0x60000L
+ field public static final long STANDARD_BT2020_CONSTANT_LUMINANCE = 458752L; // 0x70000L
+ field public static final long STANDARD_BT470M = 524288L; // 0x80000L
+ field public static final long STANDARD_BT601_525 = 262144L; // 0x40000L
+ field public static final long STANDARD_BT601_525_UNADJUSTED = 327680L; // 0x50000L
+ field public static final long STANDARD_BT601_625 = 131072L; // 0x20000L
+ field public static final long STANDARD_BT601_625_UNADJUSTED = 196608L; // 0x30000L
+ field public static final long STANDARD_BT709 = 65536L; // 0x10000L
+ field public static final long STANDARD_DCI_P3 = 655360L; // 0xa0000L
+ field public static final long STANDARD_FILM = 589824L; // 0x90000L
+ field public static final long STANDARD_UNSPECIFIED = 0L; // 0x0L
+ field public static final long TRANSFER_GAMMA2_2 = 16777216L; // 0x1000000L
+ field public static final long TRANSFER_GAMMA2_6 = 20971520L; // 0x1400000L
+ field public static final long TRANSFER_GAMMA2_8 = 25165824L; // 0x1800000L
+ field public static final long TRANSFER_HLG = 33554432L; // 0x2000000L
+ field public static final long TRANSFER_LINEAR = 4194304L; // 0x400000L
+ field public static final long TRANSFER_SMPTE_170M = 12582912L; // 0xc00000L
+ field public static final long TRANSFER_SRGB = 8388608L; // 0x800000L
+ field public static final long TRANSFER_ST2084 = 29360128L; // 0x1c00000L
+ field public static final long TRANSFER_UNSPECIFIED = 0L; // 0x0L
+ }
+
public class GeomagneticField {
ctor public GeomagneticField(float, float, float, long);
method public float getDeclination();
@@ -17474,6 +17951,7 @@
field public static final int RGB_565 = 4; // 0x4
field public static final int RGB_888 = 3; // 0x3
field public static final int S_UI8 = 53; // 0x35
+ field public static final long USAGE_COMPOSER_OVERLAY = 2048L; // 0x800L
field public static final long USAGE_CPU_READ_OFTEN = 3L; // 0x3L
field public static final long USAGE_CPU_READ_RARELY = 2L; // 0x2L
field public static final long USAGE_CPU_WRITE_OFTEN = 48L; // 0x30L
@@ -17939,7 +18417,10 @@
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> DISTORTION_CORRECTION_AVAILABLE_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> EDGE_AVAILABLE_EDGE_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> FLASH_INFO_AVAILABLE;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_INFO_STRENGTH_DEFAULT_LEVEL;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_INFO_STRENGTH_MAXIMUM_LEVEL;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap> INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> INFO_SUPPORTED_HARDWARE_LEVEL;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.String> INFO_VERSION;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES;
@@ -18074,8 +18555,9 @@
method @NonNull public java.util.List<android.util.Size> getExtensionSupportedSizes(int, int);
method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions();
field public static final int EXTENSION_AUTOMATIC = 0; // 0x0
- field public static final int EXTENSION_BEAUTY = 1; // 0x1
+ field @Deprecated public static final int EXTENSION_BEAUTY = 1; // 0x1
field public static final int EXTENSION_BOKEH = 2; // 0x2
+ field public static final int EXTENSION_FACE_RETOUCH = 1; // 0x1
field public static final int EXTENSION_HDR = 3; // 0x3
field public static final int EXTENSION_NIGHT = 4; // 0x4
}
@@ -18109,6 +18591,7 @@
method @NonNull public android.hardware.camera2.CameraExtensionCharacteristics getCameraExtensionCharacteristics(@NonNull String) throws android.hardware.camera2.CameraAccessException;
method @NonNull public String[] getCameraIdList() throws android.hardware.camera2.CameraAccessException;
method @NonNull public java.util.Set<java.util.Set<java.lang.String>> getConcurrentCameraIds() throws android.hardware.camera2.CameraAccessException;
+ method public int getTorchStrengthLevel(@NonNull String) throws android.hardware.camera2.CameraAccessException;
method @RequiresPermission(android.Manifest.permission.CAMERA) public boolean isConcurrentSessionConfigurationSupported(@NonNull java.util.Map<java.lang.String,android.hardware.camera2.params.SessionConfiguration>) throws android.hardware.camera2.CameraAccessException;
method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, @NonNull android.hardware.camera2.CameraDevice.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
@@ -18117,6 +18600,7 @@
method public void registerTorchCallback(@NonNull android.hardware.camera2.CameraManager.TorchCallback, @Nullable android.os.Handler);
method public void registerTorchCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraManager.TorchCallback);
method public void setTorchMode(@NonNull String, boolean) throws android.hardware.camera2.CameraAccessException;
+ method public void turnOnTorchWithStrengthLevel(@NonNull String, int) throws android.hardware.camera2.CameraAccessException;
method public void unregisterAvailabilityCallback(@NonNull android.hardware.camera2.CameraManager.AvailabilityCallback);
method public void unregisterTorchCallback(@NonNull android.hardware.camera2.CameraManager.TorchCallback);
}
@@ -18134,6 +18618,7 @@
ctor public CameraManager.TorchCallback();
method public void onTorchModeChanged(@NonNull String, boolean);
method public void onTorchModeUnavailable(@NonNull String);
+ method public void onTorchStrengthLevelChanged(@NonNull String, int);
}
public abstract class CameraMetadata<TKey> {
@@ -18240,6 +18725,7 @@
field public static final int CONTROL_SCENE_MODE_THEATRE = 7; // 0x7
field public static final int CONTROL_VIDEO_STABILIZATION_MODE_OFF = 0; // 0x0
field public static final int CONTROL_VIDEO_STABILIZATION_MODE_ON = 1; // 0x1
+ field public static final int CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION = 2; // 0x2
field public static final int DISTORTION_CORRECTION_MODE_FAST = 1; // 0x1
field public static final int DISTORTION_CORRECTION_MODE_HIGH_QUALITY = 2; // 0x2
field public static final int DISTORTION_CORRECTION_MODE_OFF = 0; // 0x0
@@ -18634,6 +19120,12 @@
method public android.util.Rational getElement(int, int);
}
+ public final class DeviceStateSensorOrientationMap {
+ method public int getSensorOrientation(long);
+ field public static final long FOLDED = 4L; // 0x4L
+ field public static final long NORMAL = 0L; // 0x0L
+ }
+
public final class ExtensionSessionConfiguration {
ctor public ExtensionSessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.StateCallback);
method @NonNull public java.util.concurrent.Executor getExecutor();
@@ -19572,14 +20064,22 @@
}
public final class Geocoder {
- ctor public Geocoder(android.content.Context, java.util.Locale);
- ctor public Geocoder(android.content.Context);
- method public java.util.List<android.location.Address> getFromLocation(double, double, int) throws java.io.IOException;
- method public java.util.List<android.location.Address> getFromLocationName(String, int) throws java.io.IOException;
- method public java.util.List<android.location.Address> getFromLocationName(String, int, double, double, double, double) throws java.io.IOException;
+ ctor public Geocoder(@NonNull android.content.Context);
+ ctor public Geocoder(@NonNull android.content.Context, @NonNull java.util.Locale);
+ method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocation(@FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @IntRange int) throws java.io.IOException;
+ method public void getFromLocation(@FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @IntRange int, @NonNull android.location.Geocoder.GeocodeListener);
+ method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocationName(@NonNull String, @IntRange int) throws java.io.IOException;
+ method public void getFromLocationName(@NonNull String, @IntRange int, @NonNull android.location.Geocoder.GeocodeListener);
+ method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocationName(@NonNull String, @IntRange int, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double) throws java.io.IOException;
+ method public void getFromLocationName(@NonNull String, @IntRange int, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @NonNull android.location.Geocoder.GeocodeListener);
method public static boolean isPresent();
}
+ public static interface Geocoder.GeocodeListener {
+ method public default void onError(@Nullable String);
+ method public void onGeocode(@NonNull java.util.List<android.location.Address>);
+ }
+
public final class GnssAntennaInfo implements android.os.Parcelable {
method public int describeContents();
method @FloatRange(from=0.0f) public double getCarrierFrequencyMHz();
@@ -19752,6 +20252,7 @@
public final class GnssMeasurementRequest implements android.os.Parcelable {
method public int describeContents();
+ method @IntRange(from=0) public int getIntervalMillis();
method public boolean isFullTracking();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssMeasurementRequest> CREATOR;
@@ -19762,6 +20263,7 @@
ctor public GnssMeasurementRequest.Builder(@NonNull android.location.GnssMeasurementRequest);
method @NonNull public android.location.GnssMeasurementRequest build();
method @NonNull public android.location.GnssMeasurementRequest.Builder setFullTracking(boolean);
+ method @NonNull public android.location.GnssMeasurementRequest.Builder setIntervalMillis(@IntRange(from=0) int);
}
public final class GnssMeasurementsEvent implements android.os.Parcelable {
@@ -19893,29 +20395,32 @@
}
public class Location implements android.os.Parcelable {
- ctor public Location(String);
- ctor public Location(android.location.Location);
- method public float bearingTo(android.location.Location);
- method public static String convert(double, int);
- method public static double convert(String);
+ ctor public Location(@Nullable String);
+ ctor public Location(@NonNull android.location.Location);
+ method public float bearingTo(@NonNull android.location.Location);
+ method @NonNull public static String convert(@FloatRange double, int);
+ method @FloatRange public static double convert(@NonNull String);
method public int describeContents();
- method public static void distanceBetween(double, double, double, double, float[]);
- method public float distanceTo(android.location.Location);
- method public void dump(android.util.Printer, String);
- method public float getAccuracy();
- method public double getAltitude();
- method public float getBearing();
- method public float getBearingAccuracyDegrees();
- method public long getElapsedRealtimeNanos();
- method public double getElapsedRealtimeUncertaintyNanos();
- method public android.os.Bundle getExtras();
- method public double getLatitude();
- method public double getLongitude();
- method public String getProvider();
- method public float getSpeed();
- method public float getSpeedAccuracyMetersPerSecond();
- method public long getTime();
- method public float getVerticalAccuracyMeters();
+ method public static void distanceBetween(@FloatRange double, @FloatRange double, @FloatRange double, @FloatRange double, float[]);
+ method @FloatRange public float distanceTo(@NonNull android.location.Location);
+ method public void dump(@NonNull android.util.Printer, @Nullable String);
+ method @FloatRange public float getAccuracy();
+ method @FloatRange public double getAltitude();
+ method @FloatRange(from=0.0f, to=360.0f, toInclusive=false) public float getBearing();
+ method @FloatRange public float getBearingAccuracyDegrees();
+ method @IntRange public long getElapsedRealtimeAgeMillis();
+ method @IntRange public long getElapsedRealtimeAgeMillis(@IntRange long);
+ method @IntRange public long getElapsedRealtimeMillis();
+ method @IntRange public long getElapsedRealtimeNanos();
+ method @FloatRange public double getElapsedRealtimeUncertaintyNanos();
+ method @Nullable public android.os.Bundle getExtras();
+ method @FloatRange public double getLatitude();
+ method @FloatRange public double getLongitude();
+ method @Nullable public String getProvider();
+ method @FloatRange public float getSpeed();
+ method @FloatRange public float getSpeedAccuracyMetersPerSecond();
+ method @IntRange public long getTime();
+ method @FloatRange public float getVerticalAccuracyMeters();
method public boolean hasAccuracy();
method public boolean hasAltitude();
method public boolean hasBearing();
@@ -19924,30 +20429,35 @@
method public boolean hasSpeed();
method public boolean hasSpeedAccuracy();
method public boolean hasVerticalAccuracy();
+ method public boolean isComplete();
method @Deprecated public boolean isFromMockProvider();
method public boolean isMock();
- method @Deprecated public void removeAccuracy();
- method @Deprecated public void removeAltitude();
- method @Deprecated public void removeBearing();
- method @Deprecated public void removeSpeed();
+ method public void removeAccuracy();
+ method public void removeAltitude();
+ method public void removeBearing();
+ method public void removeBearingAccuracy();
+ method public void removeElapsedRealtimeUncertaintyNanos();
+ method public void removeSpeed();
+ method public void removeSpeedAccuracy();
+ method public void removeVerticalAccuracy();
method public void reset();
- method public void set(android.location.Location);
- method public void setAccuracy(float);
- method public void setAltitude(double);
- method public void setBearing(float);
- method public void setBearingAccuracyDegrees(float);
- method public void setElapsedRealtimeNanos(long);
- method public void setElapsedRealtimeUncertaintyNanos(double);
+ method public void set(@NonNull android.location.Location);
+ method public void setAccuracy(@FloatRange float);
+ method public void setAltitude(@FloatRange double);
+ method public void setBearing(@FloatRange(fromInclusive=false, toInclusive=false) float);
+ method public void setBearingAccuracyDegrees(@FloatRange float);
+ method public void setElapsedRealtimeNanos(@IntRange long);
+ method public void setElapsedRealtimeUncertaintyNanos(@FloatRange double);
method public void setExtras(@Nullable android.os.Bundle);
- method public void setLatitude(double);
- method public void setLongitude(double);
+ method public void setLatitude(@FloatRange double);
+ method public void setLongitude(@FloatRange double);
method public void setMock(boolean);
- method public void setProvider(String);
- method public void setSpeed(float);
- method public void setSpeedAccuracyMetersPerSecond(float);
- method public void setTime(long);
- method public void setVerticalAccuracyMeters(float);
- method public void writeToParcel(android.os.Parcel, int);
+ method public void setProvider(@Nullable String);
+ method public void setSpeed(@FloatRange float);
+ method public void setSpeedAccuracyMetersPerSecond(@FloatRange float);
+ method public void setTime(@IntRange long);
+ method public void setVerticalAccuracyMeters(@FloatRange float);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.location.Location> CREATOR;
field public static final int FORMAT_DEGREES = 0; // 0x0
field public static final int FORMAT_MINUTES = 1; // 0x1
@@ -20175,8 +20685,10 @@
method public int getAllowedCapturePolicy();
method public int getContentType();
method public int getFlags();
+ method public int getSpatializationBehavior();
method public int getUsage();
method public int getVolumeControlStream();
+ method public boolean isContentSpatialized();
method public void writeToParcel(android.os.Parcel, int);
field public static final int ALLOW_CAPTURE_BY_ALL = 1; // 0x1
field public static final int ALLOW_CAPTURE_BY_NONE = 3; // 0x3
@@ -20190,6 +20702,8 @@
field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
field public static final int FLAG_HW_AV_SYNC = 16; // 0x10
field @Deprecated public static final int FLAG_LOW_LATENCY = 256; // 0x100
+ field public static final int SPATIALIZATION_BEHAVIOR_AUTO = 0; // 0x0
+ field public static final int SPATIALIZATION_BEHAVIOR_NEVER = 1; // 0x1
field public static final int USAGE_ALARM = 4; // 0x4
field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
@@ -20198,9 +20712,9 @@
field public static final int USAGE_GAME = 14; // 0xe
field public static final int USAGE_MEDIA = 1; // 0x1
field public static final int USAGE_NOTIFICATION = 5; // 0x5
- field public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9; // 0x9
- field public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8; // 0x8
- field public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7; // 0x7
+ field @Deprecated public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9; // 0x9
+ field @Deprecated public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8; // 0x8
+ field @Deprecated public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7; // 0x7
field public static final int USAGE_NOTIFICATION_EVENT = 10; // 0xa
field public static final int USAGE_NOTIFICATION_RINGTONE = 6; // 0x6
field public static final int USAGE_UNKNOWN = 0; // 0x0
@@ -20216,7 +20730,9 @@
method public android.media.AudioAttributes.Builder setContentType(int);
method public android.media.AudioAttributes.Builder setFlags(int);
method @NonNull public android.media.AudioAttributes.Builder setHapticChannelsMuted(boolean);
+ method @NonNull public android.media.AudioAttributes.Builder setIsContentSpatialized(boolean);
method public android.media.AudioAttributes.Builder setLegacyStreamType(int);
+ method @NonNull public android.media.AudioAttributes.Builder setSpatializationBehavior(int);
method public android.media.AudioAttributes.Builder setUsage(int);
}
@@ -20333,24 +20849,45 @@
field public static final int CHANNEL_IN_Y_AXIS = 4096; // 0x1000
field public static final int CHANNEL_IN_Z_AXIS = 8192; // 0x2000
field public static final int CHANNEL_OUT_5POINT1 = 252; // 0xfc
+ field public static final int CHANNEL_OUT_5POINT1POINT2 = 3145980; // 0x3000fc
+ field public static final int CHANNEL_OUT_5POINT1POINT4 = 737532; // 0xb40fc
field @Deprecated public static final int CHANNEL_OUT_7POINT1 = 1020; // 0x3fc
+ field public static final int CHANNEL_OUT_7POINT1POINT2 = 3152124; // 0x3018fc
+ field public static final int CHANNEL_OUT_7POINT1POINT4 = 743676; // 0xb58fc
field public static final int CHANNEL_OUT_7POINT1_SURROUND = 6396; // 0x18fc
+ field public static final int CHANNEL_OUT_9POINT1POINT4 = 202070268; // 0xc0b58fc
+ field public static final int CHANNEL_OUT_9POINT1POINT6 = 205215996; // 0xc3b58fc
field public static final int CHANNEL_OUT_BACK_CENTER = 1024; // 0x400
field public static final int CHANNEL_OUT_BACK_LEFT = 64; // 0x40
field public static final int CHANNEL_OUT_BACK_RIGHT = 128; // 0x80
+ field public static final int CHANNEL_OUT_BOTTOM_FRONT_CENTER = 8388608; // 0x800000
+ field public static final int CHANNEL_OUT_BOTTOM_FRONT_LEFT = 4194304; // 0x400000
+ field public static final int CHANNEL_OUT_BOTTOM_FRONT_RIGHT = 16777216; // 0x1000000
field public static final int CHANNEL_OUT_DEFAULT = 1; // 0x1
field public static final int CHANNEL_OUT_FRONT_CENTER = 16; // 0x10
field public static final int CHANNEL_OUT_FRONT_LEFT = 4; // 0x4
field public static final int CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 256; // 0x100
field public static final int CHANNEL_OUT_FRONT_RIGHT = 8; // 0x8
field public static final int CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 512; // 0x200
+ field public static final int CHANNEL_OUT_FRONT_WIDE_LEFT = 67108864; // 0x4000000
+ field public static final int CHANNEL_OUT_FRONT_WIDE_RIGHT = 134217728; // 0x8000000
field public static final int CHANNEL_OUT_LOW_FREQUENCY = 32; // 0x20
+ field public static final int CHANNEL_OUT_LOW_FREQUENCY_2 = 33554432; // 0x2000000
field public static final int CHANNEL_OUT_MONO = 4; // 0x4
field public static final int CHANNEL_OUT_QUAD = 204; // 0xcc
field public static final int CHANNEL_OUT_SIDE_LEFT = 2048; // 0x800
field public static final int CHANNEL_OUT_SIDE_RIGHT = 4096; // 0x1000
field public static final int CHANNEL_OUT_STEREO = 12; // 0xc
field public static final int CHANNEL_OUT_SURROUND = 1052; // 0x41c
+ field public static final int CHANNEL_OUT_TOP_BACK_CENTER = 262144; // 0x40000
+ field public static final int CHANNEL_OUT_TOP_BACK_LEFT = 131072; // 0x20000
+ field public static final int CHANNEL_OUT_TOP_BACK_RIGHT = 524288; // 0x80000
+ field public static final int CHANNEL_OUT_TOP_CENTER = 8192; // 0x2000
+ field public static final int CHANNEL_OUT_TOP_FRONT_CENTER = 32768; // 0x8000
+ field public static final int CHANNEL_OUT_TOP_FRONT_LEFT = 16384; // 0x4000
+ field public static final int CHANNEL_OUT_TOP_FRONT_RIGHT = 65536; // 0x10000
+ field public static final int CHANNEL_OUT_TOP_SIDE_LEFT = 1048576; // 0x100000
+ field public static final int CHANNEL_OUT_TOP_SIDE_RIGHT = 2097152; // 0x200000
field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioFormat> CREATOR;
field public static final int ENCODING_AAC_ELD = 15; // 0xf
field public static final int ENCODING_AAC_HE_V1 = 11; // 0xb
@@ -20412,14 +20949,16 @@
method @NonNull public java.util.List<android.media.AudioDeviceInfo> getAvailableCommunicationDevices();
method @Nullable public android.media.AudioDeviceInfo getCommunicationDevice();
method public android.media.AudioDeviceInfo[] getDevices(int);
+ method public static int getDirectPlaybackSupport(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
method public int getEncodedSurroundMode();
method public java.util.List<android.media.MicrophoneInfo> getMicrophones() throws java.io.IOException;
method public int getMode();
method public String getParameters(String);
- method public static int getPlaybackOffloadSupport(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
+ method @Deprecated public static int getPlaybackOffloadSupport(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
method public String getProperty(String);
method public int getRingerMode();
method @Deprecated public int getRouting(int);
+ method @NonNull public android.media.Spatializer getSpatializer();
method public int getStreamMaxVolume(int);
method public int getStreamMinVolume(int);
method public int getStreamVolume(int);
@@ -20433,6 +20972,7 @@
method public boolean isMicrophoneMute();
method public boolean isMusicActive();
method public static boolean isOffloadedPlaybackSupported(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
+ method public boolean isRampingRingerEnabled();
method public boolean isSpeakerphoneOn();
method public boolean isStreamMute(int);
method public boolean isSurroundFormatEnabled(int);
@@ -20505,6 +21045,10 @@
field public static final int AUDIOFOCUS_REQUEST_FAILED = 0; // 0x0
field public static final int AUDIOFOCUS_REQUEST_GRANTED = 1; // 0x1
field public static final int AUDIO_SESSION_ID_GENERATE = 0; // 0x0
+ field public static final int DIRECT_PLAYBACK_BITSTREAM_SUPPORTED = 4; // 0x4
+ field public static final int DIRECT_PLAYBACK_NOT_SUPPORTED = 0; // 0x0
+ field public static final int DIRECT_PLAYBACK_OFFLOAD_GAPLESS_SUPPORTED = 3; // 0x3
+ field public static final int DIRECT_PLAYBACK_OFFLOAD_SUPPORTED = 1; // 0x1
field public static final int ENCODED_SURROUND_OUTPUT_ALWAYS = 2; // 0x2
field public static final int ENCODED_SURROUND_OUTPUT_AUTO = 0; // 0x0
field public static final int ENCODED_SURROUND_OUTPUT_MANUAL = 3; // 0x3
@@ -20539,7 +21083,9 @@
field public static final int GET_DEVICES_ALL = 3; // 0x3
field public static final int GET_DEVICES_INPUTS = 1; // 0x1
field public static final int GET_DEVICES_OUTPUTS = 2; // 0x2
+ field public static final int MODE_CALL_REDIRECT = 5; // 0x5
field public static final int MODE_CALL_SCREENING = 4; // 0x4
+ field public static final int MODE_COMMUNICATION_REDIRECT = 6; // 0x6
field public static final int MODE_CURRENT = -1; // 0xffffffff
field public static final int MODE_INVALID = -2; // 0xfffffffe
field public static final int MODE_IN_CALL = 2; // 0x2
@@ -20893,7 +21439,7 @@
method public int getStreamType();
method public boolean getTimestamp(android.media.AudioTimestamp);
method public int getUnderrunCount();
- method public static boolean isDirectPlaybackSupported(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
+ method @Deprecated public static boolean isDirectPlaybackSupported(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
method public boolean isOffloadedPlayback();
method public void pause() throws java.lang.IllegalStateException;
method public void play() throws java.lang.IllegalStateException;
@@ -21317,6 +21863,7 @@
public abstract class Image implements java.lang.AutoCloseable {
method public abstract void close();
method public android.graphics.Rect getCropRect();
+ method public long getDataSpace();
method public abstract int getFormat();
method @Nullable public android.hardware.HardwareBuffer getHardwareBuffer();
method public abstract int getHeight();
@@ -21324,6 +21871,7 @@
method public abstract long getTimestamp();
method public abstract int getWidth();
method public void setCropRect(android.graphics.Rect);
+ method public void setDataSpace(long);
method public void setTimestamp(long);
}
@@ -21399,6 +21947,7 @@
public class MediaActionSound {
ctor public MediaActionSound();
method public void load(int);
+ method public static boolean mustPlayShutterSound();
method public void play(int);
method public void release();
field public static final int FOCUS_COMPLETE = 1; // 0x1
@@ -22420,7 +22969,7 @@
method public void setDataSource(@NonNull java.io.FileDescriptor) throws java.io.IOException;
method public void setDataSource(@NonNull java.io.FileDescriptor, long, long) throws java.io.IOException;
method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
- method public void setMediaCas(@NonNull android.media.MediaCas);
+ method @Deprecated public void setMediaCas(@NonNull android.media.MediaCas);
method public void unselectTrack(int);
field public static final int SAMPLE_FLAG_ENCRYPTED = 2; // 0x2
field public static final int SAMPLE_FLAG_PARTIAL_FRAME = 4; // 0x4
@@ -22537,6 +23086,7 @@
field public static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder";
field public static final String KEY_MAX_HEIGHT = "max-height";
field public static final String KEY_MAX_INPUT_SIZE = "max-input-size";
+ field public static final String KEY_MAX_OUTPUT_CHANNEL_COUNT = "max-output-channel-count";
field public static final String KEY_MAX_PTS_GAP_TO_ENCODER = "max-pts-gap-to-encoder";
field public static final String KEY_MAX_WIDTH = "max-width";
field public static final String KEY_MIME = "mime";
@@ -22573,16 +23123,32 @@
field public static final String KEY_VIDEO_QP_P_MIN = "video-qp-p-min";
field public static final String KEY_WIDTH = "width";
field public static final String MIMETYPE_AUDIO_AAC = "audio/mp4a-latm";
+ field public static final String MIMETYPE_AUDIO_AAC_ELD = "audio/mp4a.40.39";
+ field public static final String MIMETYPE_AUDIO_AAC_HE_V1 = "audio/mp4a.40.05";
+ field public static final String MIMETYPE_AUDIO_AAC_HE_V2 = "audio/mp4a.40.29";
+ field public static final String MIMETYPE_AUDIO_AAC_LC = "audio/mp4a.40.02";
+ field public static final String MIMETYPE_AUDIO_AAC_XHE = "audio/mp4a.40.42";
field public static final String MIMETYPE_AUDIO_AC3 = "audio/ac3";
field public static final String MIMETYPE_AUDIO_AC4 = "audio/ac4";
field public static final String MIMETYPE_AUDIO_AMR_NB = "audio/3gpp";
field public static final String MIMETYPE_AUDIO_AMR_WB = "audio/amr-wb";
+ field public static final String MIMETYPE_AUDIO_DOLBY_MAT = "audio/vnd.dolby.mat";
+ field public static final String MIMETYPE_AUDIO_DOLBY_TRUEHD = "audio/vnd.dolby.mlp";
+ field public static final String MIMETYPE_AUDIO_DRA = "audio/vnd.dra";
+ field public static final String MIMETYPE_AUDIO_DTS = "audio/vnd.dts";
+ field public static final String MIMETYPE_AUDIO_DTS_HD = "audio/vnd.dts.hd";
+ field public static final String MIMETYPE_AUDIO_DTS_UHD = "audio/vnd.dts.uhd";
field public static final String MIMETYPE_AUDIO_EAC3 = "audio/eac3";
field public static final String MIMETYPE_AUDIO_EAC3_JOC = "audio/eac3-joc";
field public static final String MIMETYPE_AUDIO_FLAC = "audio/flac";
field public static final String MIMETYPE_AUDIO_G711_ALAW = "audio/g711-alaw";
field public static final String MIMETYPE_AUDIO_G711_MLAW = "audio/g711-mlaw";
+ field public static final String MIMETYPE_AUDIO_IEC61937 = "audio/x-iec61937";
field public static final String MIMETYPE_AUDIO_MPEG = "audio/mpeg";
+ field public static final String MIMETYPE_AUDIO_MPEGH_BL_L3 = "audio/mhm1.03";
+ field public static final String MIMETYPE_AUDIO_MPEGH_BL_L4 = "audio/mhm1.04";
+ field public static final String MIMETYPE_AUDIO_MPEGH_LC_L3 = "audio/mhm1.0d";
+ field public static final String MIMETYPE_AUDIO_MPEGH_LC_L4 = "audio/mhm1.0e";
field public static final String MIMETYPE_AUDIO_MPEGH_MHA1 = "audio/mha1";
field public static final String MIMETYPE_AUDIO_MPEGH_MHM1 = "audio/mhm1";
field public static final String MIMETYPE_AUDIO_MSGSM = "audio/gsm";
@@ -22693,7 +23259,7 @@
public class MediaMetadataRetriever implements java.lang.AutoCloseable {
ctor public MediaMetadataRetriever();
- method public void close();
+ method public void close() throws java.io.IOException;
method @Nullable public String extractMetadata(int);
method @Nullable public byte[] getEmbeddedPicture();
method @Nullable public android.graphics.Bitmap getFrameAtIndex(int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
@@ -22710,7 +23276,7 @@
method @Nullable public android.graphics.Bitmap getPrimaryImage();
method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, @IntRange(from=1) int, @IntRange(from=1) int);
method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, @IntRange(from=1) int, @IntRange(from=1) int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
- method public void release();
+ method public void release() throws java.io.IOException;
method public void setDataSource(String) throws java.lang.IllegalArgumentException;
method public void setDataSource(String, java.util.Map<java.lang.String,java.lang.String>) throws java.lang.IllegalArgumentException;
method public void setDataSource(java.io.FileDescriptor, long, long) throws java.lang.IllegalArgumentException;
@@ -23816,6 +24382,23 @@
method public void onLoadComplete(android.media.SoundPool, int, int);
}
+ public class Spatializer {
+ method public void addOnSpatializerStateChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerStateChangedListener);
+ method public boolean canBeSpatialized(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioFormat);
+ method public int getImmersiveAudioLevel();
+ method public boolean isAvailable();
+ method public boolean isEnabled();
+ method public void removeOnSpatializerStateChangedListener(@NonNull android.media.Spatializer.OnSpatializerStateChangedListener);
+ field public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1; // 0x1
+ field public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0; // 0x0
+ field public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1; // 0xffffffff
+ }
+
+ public static interface Spatializer.OnSpatializerStateChangedListener {
+ method public void onSpatializerAvailableChanged(@NonNull android.media.Spatializer, boolean);
+ method public void onSpatializerEnabledChanged(@NonNull android.media.Spatializer, boolean);
+ }
+
public final class SubtitleData {
ctor public SubtitleData(int, long, long, @NonNull byte[]);
method @NonNull public byte[] getData();
@@ -25229,13 +25812,17 @@
public final class MediaSessionManager {
method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName);
method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName, @Nullable android.os.Handler);
+ method public void addOnMediaKeyEventSessionChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
method public void addOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener);
method public void addOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener, @NonNull android.os.Handler);
method @NonNull public java.util.List<android.media.session.MediaController> getActiveSessions(@Nullable android.content.ComponentName);
+ method @Nullable public android.media.session.MediaSession.Token getMediaKeyEventSession();
+ method @NonNull public String getMediaKeyEventSessionPackageName();
method @NonNull public java.util.List<android.media.Session2Token> getSession2Tokens();
method public boolean isTrustedForMediaControl(@NonNull android.media.session.MediaSessionManager.RemoteUserInfo);
method @Deprecated public void notifySession2Created(@NonNull android.media.Session2Token);
method public void removeOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener);
+ method public void removeOnMediaKeyEventSessionChangedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
method public void removeOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener);
}
@@ -25243,6 +25830,10 @@
method public void onActiveSessionsChanged(@Nullable java.util.List<android.media.session.MediaController>);
}
+ public static interface MediaSessionManager.OnMediaKeyEventSessionChangedListener {
+ method public void onMediaKeyEventSessionChanged(@NonNull String, @Nullable android.media.session.MediaSession.Token);
+ }
+
public static interface MediaSessionManager.OnSession2TokensChangedListener {
method public void onSession2TokensChanged(@NonNull java.util.List<android.media.Session2Token>);
}
@@ -25496,6 +26087,7 @@
field public static final String COLUMN_CONTENT_ID = "content_id";
field public static final String COLUMN_CONTENT_RATING = "content_rating";
field public static final String COLUMN_DURATION_MILLIS = "duration_millis";
+ field public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
field public static final String COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number";
field public static final String COLUMN_EPISODE_TITLE = "episode_title";
field public static final String COLUMN_INTENT_URI = "intent_uri";
@@ -25526,6 +26118,7 @@
field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
field public static final String COLUMN_SPLIT_ID = "split_id";
field public static final String COLUMN_STARTING_PRICE = "starting_price";
+ field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
field public static final String COLUMN_TITLE = "title";
@@ -25579,11 +26172,14 @@
field public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
field public static final String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
field public static final String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+ field public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
field public static final String COLUMN_LONG_DESCRIPTION = "long_description";
+ field public static final String COLUMN_MULTI_SERIES_ID = "multi_series_id";
field public static final String COLUMN_POSTER_ART_URI = "poster_art_uri";
field public static final String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
field public static final String COLUMN_REVIEW_RATING = "review_rating";
field public static final String COLUMN_REVIEW_RATING_STYLE = "review_rating_style";
+ field public static final String COLUMN_SCRAMBLED = "scrambled";
field public static final String COLUMN_SEARCHABLE = "searchable";
field public static final String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number";
field @Deprecated public static final String COLUMN_SEASON_NUMBER = "season_number";
@@ -25643,7 +26239,9 @@
field public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
field public static final String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
field public static final String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+ field public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
field public static final String COLUMN_LONG_DESCRIPTION = "long_description";
+ field public static final String COLUMN_MULTI_SERIES_ID = "multi_series_id";
field public static final String COLUMN_POSTER_ART_URI = "poster_art_uri";
field public static final String COLUMN_RECORDING_DATA_BYTES = "recording_data_bytes";
field public static final String COLUMN_RECORDING_DATA_URI = "recording_data_uri";
@@ -25688,6 +26286,7 @@
field public static final String COLUMN_CONTENT_ID = "content_id";
field public static final String COLUMN_CONTENT_RATING = "content_rating";
field public static final String COLUMN_DURATION_MILLIS = "duration_millis";
+ field public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
field public static final String COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number";
field public static final String COLUMN_EPISODE_TITLE = "episode_title";
field public static final String COLUMN_INTENT_URI = "intent_uri";
@@ -25719,6 +26318,7 @@
field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
field public static final String COLUMN_SPLIT_ID = "split_id";
field public static final String COLUMN_STARTING_PRICE = "starting_price";
+ field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
field public static final String COLUMN_TITLE = "title";
@@ -26060,6 +26660,20 @@
}
+package android.media.tv.interactive {
+
+ public final class TvIAppManager {
+ }
+
+ public abstract class TvIAppService extends android.app.Service {
+ ctor public TvIAppService();
+ method public final android.os.IBinder onBind(android.content.Intent);
+ field public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvIAppService";
+ field public static final String SERVICE_META_DATA = "android.media.tv.interactive.app";
+ }
+
+}
+
package android.mtp {
public final class MtpConstants {
@@ -26826,65 +27440,6 @@
}
-package android.net.nsd {
-
- public final class NsdManager {
- method public void discoverServices(String, int, android.net.nsd.NsdManager.DiscoveryListener);
- method public void registerService(android.net.nsd.NsdServiceInfo, int, android.net.nsd.NsdManager.RegistrationListener);
- method public void resolveService(android.net.nsd.NsdServiceInfo, android.net.nsd.NsdManager.ResolveListener);
- method public void stopServiceDiscovery(android.net.nsd.NsdManager.DiscoveryListener);
- method public void unregisterService(android.net.nsd.NsdManager.RegistrationListener);
- field public static final String ACTION_NSD_STATE_CHANGED = "android.net.nsd.STATE_CHANGED";
- field public static final String EXTRA_NSD_STATE = "nsd_state";
- field public static final int FAILURE_ALREADY_ACTIVE = 3; // 0x3
- field public static final int FAILURE_INTERNAL_ERROR = 0; // 0x0
- field public static final int FAILURE_MAX_LIMIT = 4; // 0x4
- field public static final int NSD_STATE_DISABLED = 1; // 0x1
- field public static final int NSD_STATE_ENABLED = 2; // 0x2
- field public static final int PROTOCOL_DNS_SD = 1; // 0x1
- }
-
- public static interface NsdManager.DiscoveryListener {
- method public void onDiscoveryStarted(String);
- method public void onDiscoveryStopped(String);
- method public void onServiceFound(android.net.nsd.NsdServiceInfo);
- method public void onServiceLost(android.net.nsd.NsdServiceInfo);
- method public void onStartDiscoveryFailed(String, int);
- method public void onStopDiscoveryFailed(String, int);
- }
-
- public static interface NsdManager.RegistrationListener {
- method public void onRegistrationFailed(android.net.nsd.NsdServiceInfo, int);
- method public void onServiceRegistered(android.net.nsd.NsdServiceInfo);
- method public void onServiceUnregistered(android.net.nsd.NsdServiceInfo);
- method public void onUnregistrationFailed(android.net.nsd.NsdServiceInfo, int);
- }
-
- public static interface NsdManager.ResolveListener {
- method public void onResolveFailed(android.net.nsd.NsdServiceInfo, int);
- method public void onServiceResolved(android.net.nsd.NsdServiceInfo);
- }
-
- public final class NsdServiceInfo implements android.os.Parcelable {
- ctor public NsdServiceInfo();
- method public int describeContents();
- method public java.util.Map<java.lang.String,byte[]> getAttributes();
- method public java.net.InetAddress getHost();
- method public int getPort();
- method public String getServiceName();
- method public String getServiceType();
- method public void removeAttribute(String);
- method public void setAttribute(String, String);
- method public void setHost(java.net.InetAddress);
- method public void setPort(int);
- method public void setServiceName(String);
- method public void setServiceType(String);
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.nsd.NsdServiceInfo> CREATOR;
- }
-
-}
-
package android.net.rtp {
@Deprecated public class AudioCodec {
@@ -27349,6 +27904,7 @@
field public static final String CATEGORY_PAYMENT = "payment";
field public static final String EXTRA_CATEGORY = "category";
field public static final String EXTRA_SERVICE_COMPONENT = "component";
+ field public static final String EXTRA_USERID = "android.nfc.cardemulation.extra.USERID";
field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
@@ -30794,6 +31350,8 @@
field public static final int Q = 29; // 0x1d
field public static final int R = 30; // 0x1e
field public static final int S = 31; // 0x1f
+ field public static final int S_V2 = 32; // 0x20
+ field public static final int TIRAMISU = 10000; // 0x2710
}
public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
@@ -31389,6 +31947,8 @@
method @Nullable public double[] createDoubleArray();
method @Nullable public float[] createFloatArray();
method @Nullable public int[] createIntArray();
+ method @Nullable public <T extends android.os.IInterface> T[] createInterfaceArray(@NonNull java.util.function.IntFunction<T[]>, @NonNull java.util.function.Function<android.os.IBinder,T>);
+ method @Nullable public <T extends android.os.IInterface> java.util.ArrayList<T> createInterfaceArrayList(@NonNull java.util.function.Function<android.os.IBinder,T>);
method @Nullable public long[] createLongArray();
method @Nullable public String[] createStringArray();
method @Nullable public java.util.ArrayList<java.lang.String> createStringArrayList();
@@ -31401,13 +31961,19 @@
method public int dataPosition();
method public int dataSize();
method public void enforceInterface(@NonNull String);
+ method public void enforceNoDataAvail();
method public boolean hasFileDescriptors();
+ method public boolean hasFileDescriptors(int, int);
method public byte[] marshall();
method @NonNull public static android.os.Parcel obtain();
- method @Nullable public Object[] readArray(@Nullable ClassLoader);
- method @Nullable public java.util.ArrayList readArrayList(@Nullable ClassLoader);
+ method @NonNull public static android.os.Parcel obtain(@NonNull android.os.IBinder);
+ method @Deprecated @Nullable public Object[] readArray(@Nullable ClassLoader);
+ method @Nullable public <T> T[] readArray(@Nullable ClassLoader, @NonNull Class<T>);
+ method @Deprecated @Nullable public java.util.ArrayList readArrayList(@Nullable ClassLoader);
+ method @Nullable public <T> java.util.ArrayList<T> readArrayList(@Nullable ClassLoader, @NonNull Class<? extends T>);
method public void readBinderArray(@NonNull android.os.IBinder[]);
method public void readBinderList(@NonNull java.util.List<android.os.IBinder>);
+ method @Nullable public byte[] readBlob();
method public boolean readBoolean();
method public void readBooleanArray(@NonNull boolean[]);
method @Nullable public android.os.Bundle readBundle();
@@ -31422,23 +31988,34 @@
method public android.os.ParcelFileDescriptor readFileDescriptor();
method public float readFloat();
method public void readFloatArray(@NonNull float[]);
- method @Nullable public java.util.HashMap readHashMap(@Nullable ClassLoader);
+ method @Deprecated @Nullable public java.util.HashMap readHashMap(@Nullable ClassLoader);
+ method @Nullable public <K, V> java.util.HashMap<K,V> readHashMap(@Nullable ClassLoader, @NonNull Class<? extends K>, @NonNull Class<? extends V>);
method public int readInt();
method public void readIntArray(@NonNull int[]);
- method public void readList(@NonNull java.util.List, @Nullable ClassLoader);
+ method public <T extends android.os.IInterface> void readInterfaceArray(@NonNull T[], @NonNull java.util.function.Function<android.os.IBinder,T>);
+ method public <T extends android.os.IInterface> void readInterfaceList(@NonNull java.util.List<T>, @NonNull java.util.function.Function<android.os.IBinder,T>);
+ method @Deprecated public void readList(@NonNull java.util.List, @Nullable ClassLoader);
+ method public <T> void readList(@NonNull java.util.List<? super T>, @Nullable ClassLoader, @NonNull Class<T>);
method public long readLong();
method public void readLongArray(@NonNull long[]);
- method public void readMap(@NonNull java.util.Map, @Nullable ClassLoader);
- method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
- method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
- method @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
- method @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
+ method @Deprecated public void readMap(@NonNull java.util.Map, @Nullable ClassLoader);
+ method public <K, V> void readMap(@NonNull java.util.Map<? super K,? super V>, @Nullable ClassLoader, @NonNull Class<K>, @NonNull Class<V>);
+ method @Deprecated @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
+ method @Nullable public <T> T readParcelable(@Nullable ClassLoader, @NonNull Class<T>);
+ method @Deprecated @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
+ method @Nullable public <T> T[] readParcelableArray(@Nullable ClassLoader, @NonNull Class<T>);
+ method @Deprecated @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
+ method @Nullable public <T> android.os.Parcelable.Creator<T> readParcelableCreator(@Nullable ClassLoader, @NonNull Class<T>);
+ method @Deprecated @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
+ method @NonNull public <T> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader, @NonNull Class<T>);
method @Nullable public android.os.PersistableBundle readPersistableBundle();
method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
- method @Nullable public java.io.Serializable readSerializable();
+ method @Deprecated @Nullable public java.io.Serializable readSerializable();
+ method @Nullable public <T> T readSerializable(@Nullable ClassLoader, @NonNull Class<T>);
method @NonNull public android.util.Size readSize();
method @NonNull public android.util.SizeF readSizeF();
- method @Nullable public <T> android.util.SparseArray<T> readSparseArray(@Nullable ClassLoader);
+ method @Deprecated @Nullable public <T> android.util.SparseArray<T> readSparseArray(@Nullable ClassLoader);
+ method @Nullable public <T> android.util.SparseArray<T> readSparseArray(@Nullable ClassLoader, @NonNull Class<? extends T>);
method @Nullable public android.util.SparseBooleanArray readSparseBooleanArray();
method @Nullable public String readString();
method public void readStringArray(@NonNull String[]);
@@ -31456,6 +32033,8 @@
method public void writeArray(@Nullable Object[]);
method public void writeBinderArray(@Nullable android.os.IBinder[]);
method public void writeBinderList(@Nullable java.util.List<android.os.IBinder>);
+ method public void writeBlob(@Nullable byte[]);
+ method public void writeBlob(@Nullable byte[], int, int);
method public void writeBoolean(boolean);
method public void writeBooleanArray(@Nullable boolean[]);
method public void writeBundle(@Nullable android.os.Bundle);
@@ -31471,6 +32050,8 @@
method public void writeFloatArray(@Nullable float[]);
method public void writeInt(int);
method public void writeIntArray(@Nullable int[]);
+ method public <T extends android.os.IInterface> void writeInterfaceArray(@Nullable T[]);
+ method public <T extends android.os.IInterface> void writeInterfaceList(@Nullable java.util.List<T>);
method public void writeInterfaceToken(@NonNull String);
method public void writeList(@Nullable java.util.List);
method public void writeLong(long);
@@ -31570,7 +32151,7 @@
public interface Parcelable {
method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final int CONTENTS_FILE_DESCRIPTOR = 1; // 0x1
field public static final int PARCELABLE_WRITE_RETURN_VALUE = 1; // 0x1
}
@@ -31636,6 +32217,7 @@
method public float getThermalHeadroom(@IntRange(from=0, to=60) int);
method public boolean isBatteryDischargePredictionPersonalized();
method public boolean isDeviceIdleMode();
+ method public boolean isDeviceLightIdleMode();
method public boolean isIgnoringBatteryOptimizations(String);
method public boolean isInteractive();
method public boolean isPowerSaveMode();
@@ -31648,6 +32230,7 @@
method public void removeThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener);
field public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
+ field public static final String ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
field public static final String ACTION_POWER_SAVE_MODE_CHANGED = "android.os.action.POWER_SAVE_MODE_CHANGED";
field @Deprecated public static final int FULL_WAKE_LOCK = 26; // 0x1a
field public static final int LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2; // 0x2
@@ -31689,8 +32272,10 @@
method public static final long getElapsedCpuTime();
method public static final int[] getExclusiveCores();
method public static final int getGidForName(String);
- method public static final long getStartElapsedRealtime();
- method public static final long getStartUptimeMillis();
+ method public static long getStartElapsedRealtime();
+ method public static long getStartRequestedElapsedRealtime();
+ method public static long getStartRequestedUptimeMillis();
+ method public static long getStartUptimeMillis();
method public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException;
method public static final int getUidForName(String);
method public static final boolean is64Bit();
@@ -31698,6 +32283,7 @@
method public static final boolean isIsolated();
method public static final void killProcess(int);
method public static final int myPid();
+ method @NonNull public static String myProcessName();
method public static final int myTid();
method public static final int myUid();
method public static android.os.UserHandle myUserHandle();
@@ -31787,6 +32373,7 @@
method public void close();
method @NonNull public static android.os.SharedMemory create(@Nullable String, int) throws android.system.ErrnoException;
method public int describeContents();
+ method @NonNull public static android.os.SharedMemory fromFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
method public int getSize();
method @NonNull public java.nio.ByteBuffer map(int, int, int) throws android.system.ErrnoException;
method @NonNull public java.nio.ByteBuffer mapReadOnly() throws android.system.ErrnoException;
@@ -31959,7 +32546,7 @@
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS"}) public int getUserCount();
method public long getUserCreationTime(android.os.UserHandle);
method public android.os.UserHandle getUserForSerialNumber(long);
- method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, "android.permission.CREATE_USERS"}, conditional=true) public String getUserName();
+ method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS", "android.permission.QUERY_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public String getUserName();
method public java.util.List<android.os.UserHandle> getUserProfiles();
method public android.os.Bundle getUserRestrictions();
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
@@ -31985,6 +32572,7 @@
field public static final String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
field @Deprecated public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
field public static final String DISALLOW_ADD_USER = "no_add_user";
+ field public static final String DISALLOW_ADD_WIFI_CONFIG = "no_add_wifi_config";
field public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
field public static final String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
field public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display";
@@ -31993,6 +32581,7 @@
field public static final String DISALLOW_BLUETOOTH = "no_bluetooth";
field public static final String DISALLOW_BLUETOOTH_SHARING = "no_bluetooth_sharing";
field public static final String DISALLOW_CAMERA_TOGGLE = "disallow_camera_toggle";
+ field public static final String DISALLOW_CHANGE_WIFI_STATE = "no_change_wifi_state";
field public static final String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
field public static final String DISALLOW_CONFIG_BRIGHTNESS = "no_config_brightness";
field public static final String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
@@ -32031,6 +32620,7 @@
field public static final String DISALLOW_SET_WALLPAPER = "no_set_wallpaper";
field public static final String DISALLOW_SHARE_INTO_MANAGED_PROFILE = "no_sharing_into_profile";
field public static final String DISALLOW_SHARE_LOCATION = "no_share_location";
+ field public static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI = "no_sharing_admin_configured_wifi";
field public static final String DISALLOW_SMS = "no_sms";
field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
field public static final String DISALLOW_UNIFIED_PASSWORD = "no_unified_password";
@@ -32038,6 +32628,8 @@
field public static final String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
field public static final String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";
field public static final String DISALLOW_USER_SWITCH = "no_user_switch";
+ field public static final String DISALLOW_WIFI_DIRECT = "no_wifi_direct";
+ field public static final String DISALLOW_WIFI_TETHERING = "no_wifi_tethering";
field public static final String ENSURE_VERIFY_APPS = "ensure_verify_apps";
field public static final String KEY_RESTRICTIONS_PENDING = "restrictions_pending";
field public static final int QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED = 1; // 0x1
@@ -32057,6 +32649,7 @@
}
public final class VibrationAttributes implements android.os.Parcelable {
+ method @NonNull public static android.os.VibrationAttributes createForUsage(int);
method public int describeContents();
method public int getFlags();
method public int getUsage();
@@ -32065,13 +32658,16 @@
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationAttributes> CREATOR;
field public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 1; // 0x1
+ field public static final int USAGE_ACCESSIBILITY = 66; // 0x42
field public static final int USAGE_ALARM = 17; // 0x11
field public static final int USAGE_CLASS_ALARM = 1; // 0x1
field public static final int USAGE_CLASS_FEEDBACK = 2; // 0x2
field public static final int USAGE_CLASS_MASK = 15; // 0xf
+ field public static final int USAGE_CLASS_MEDIA = 3; // 0x3
field public static final int USAGE_CLASS_UNKNOWN = 0; // 0x0
field public static final int USAGE_COMMUNICATION_REQUEST = 65; // 0x41
field public static final int USAGE_HARDWARE_FEEDBACK = 50; // 0x32
+ field public static final int USAGE_MEDIA = 19; // 0x13
field public static final int USAGE_NOTIFICATION = 49; // 0x31
field public static final int USAGE_PHYSICAL_EMULATION = 34; // 0x22
field public static final int USAGE_RINGTONE = 33; // 0x21
@@ -32082,6 +32678,7 @@
public static final class VibrationAttributes.Builder {
ctor public VibrationAttributes.Builder();
ctor public VibrationAttributes.Builder(@Nullable android.os.VibrationAttributes);
+ ctor public VibrationAttributes.Builder(@NonNull android.media.AudioAttributes);
method @NonNull public android.os.VibrationAttributes build();
method @NonNull public android.os.VibrationAttributes.Builder setFlags(int, int);
method @NonNull public android.os.VibrationAttributes.Builder setUsage(int);
@@ -32132,7 +32729,8 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long[], int);
method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long[], int, android.media.AudioAttributes);
method @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(android.os.VibrationEffect);
- method @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
+ method @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(@NonNull android.os.VibrationEffect, @NonNull android.os.VibrationAttributes);
field public static final int VIBRATION_EFFECT_SUPPORT_NO = 2; // 0x2
field public static final int VIBRATION_EFFECT_SUPPORT_UNKNOWN = 0; // 0x0
field public static final int VIBRATION_EFFECT_SUPPORT_YES = 1; // 0x1
@@ -34786,6 +35384,8 @@
}
public static final class ContactsContract.Settings implements android.provider.ContactsContract.SettingsColumns {
+ method @Nullable public static android.accounts.Account getDefaultAccount(@NonNull android.content.ContentResolver);
+ field public static final String ACTION_SET_DEFAULT_ACCOUNT = "android.provider.action.SET_DEFAULT_ACCOUNT";
field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/setting";
field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/setting";
field public static final android.net.Uri CONTENT_URI;
@@ -35115,10 +35715,12 @@
field public static final String ACTION_ACCESSIBILITY_SETTINGS = "android.settings.ACCESSIBILITY_SETTINGS";
field public static final String ACTION_ADD_ACCOUNT = "android.settings.ADD_ACCOUNT_SETTINGS";
field public static final String ACTION_AIRPLANE_MODE_SETTINGS = "android.settings.AIRPLANE_MODE_SETTINGS";
+ field public static final String ACTION_ALL_APPS_NOTIFICATION_SETTINGS = "android.settings.ALL_APPS_NOTIFICATION_SETTINGS";
field public static final String ACTION_APN_SETTINGS = "android.settings.APN_SETTINGS";
field public static final String ACTION_APPLICATION_DETAILS_SETTINGS = "android.settings.APPLICATION_DETAILS_SETTINGS";
field public static final String ACTION_APPLICATION_DEVELOPMENT_SETTINGS = "android.settings.APPLICATION_DEVELOPMENT_SETTINGS";
field public static final String ACTION_APPLICATION_SETTINGS = "android.settings.APPLICATION_SETTINGS";
+ field public static final String ACTION_APP_LOCALE_SETTINGS = "android.settings.APP_LOCALE_SETTINGS";
field public static final String ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS = "android.settings.APP_NOTIFICATION_BUBBLE_SETTINGS";
field public static final String ACTION_APP_NOTIFICATION_SETTINGS = "android.settings.APP_NOTIFICATION_SETTINGS";
field public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS = "android.settings.APP_OPEN_BY_DEFAULT_SETTINGS";
@@ -35155,6 +35757,7 @@
field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
field public static final String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION";
+ field public static final String ACTION_MANAGE_SUPERVISOR_RESTRICTED_SETTING = "android.settings.MANAGE_SUPERVISOR_RESTRICTED_SETTING";
field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES";
field public static final String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS";
field public static final String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS";
@@ -35179,6 +35782,7 @@
field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
field public static final String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
field public static final String ACTION_SETTINGS = "android.settings.SETTINGS";
+ field public static final String ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY = "android.settings.SETTINGS_EMBED_DEEP_LINK_ACTIVITY";
field public static final String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
field public static final String ACTION_SHOW_WORK_POLICY_INFO = "android.settings.SHOW_WORK_POLICY_INFO";
field public static final String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS";
@@ -35219,11 +35823,16 @@
field public static final String EXTRA_EASY_CONNECT_ERROR_CODE = "android.provider.extra.EASY_CONNECT_ERROR_CODE";
field public static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
field public static final String EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME = "android.provider.extra.NOTIFICATION_LISTENER_COMPONENT_NAME";
+ field public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY = "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY";
+ field public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI = "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI";
field public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
+ field public static final String EXTRA_SUPERVISOR_RESTRICTED_SETTING_KEY = "android.provider.extra.SUPERVISOR_RESTRICTED_SETTING_KEY";
field public static final String EXTRA_WIFI_NETWORK_LIST = "android.provider.extra.WIFI_NETWORK_LIST";
field public static final String EXTRA_WIFI_NETWORK_RESULT_LIST = "android.provider.extra.WIFI_NETWORK_RESULT_LIST";
field public static final String INTENT_CATEGORY_USAGE_ACCESS_CONFIG = "android.intent.category.USAGE_ACCESS_CONFIG";
field public static final String METADATA_USAGE_ACCESS_REASON = "android.settings.metadata.USAGE_ACCESS_REASON";
+ field public static final String SUPERVISOR_VERIFICATION_SETTING_BIOMETRICS = "supervisor_restricted_biometrics_controller";
+ field public static final String SUPERVISOR_VERIFICATION_SETTING_UNKNOWN = "";
}
public static final class Settings.Global extends android.provider.Settings.NameValueTable {
@@ -35245,7 +35854,7 @@
field public static final String AIRPLANE_MODE_RADIOS = "airplane_mode_radios";
field public static final String ALWAYS_FINISH_ACTIVITIES = "always_finish_activities";
field public static final String ANIMATOR_DURATION_SCALE = "animator_duration_scale";
- field public static final String APPLY_RAMPING_RINGER = "apply_ramping_ringer";
+ field @Deprecated public static final String APPLY_RAMPING_RINGER = "apply_ramping_ringer";
field public static final String AUTO_TIME = "auto_time";
field public static final String AUTO_TIME_ZONE = "auto_time_zone";
field public static final String BLUETOOTH_ON = "bluetooth_on";
@@ -37278,6 +37887,28 @@
method @Deprecated @NonNull public android.security.KeyPairGeneratorSpec.Builder setSubject(@NonNull javax.security.auth.x500.X500Principal);
}
+ public class KeyStoreException extends java.lang.Exception {
+ method public int getNumericErrorCode();
+ method public boolean isSystemError();
+ method public boolean isTransientFailure();
+ method public boolean requiresUserAuthentication();
+ field public static final int ERROR_ATTESTATION_CHALLENGE_TOO_LARGE = 9; // 0x9
+ field public static final int ERROR_ID_ATTESTATION_FAILURE = 8; // 0x8
+ field public static final int ERROR_INCORRECT_USAGE = 13; // 0xd
+ field public static final int ERROR_INTERNAL_SYSTEM_ERROR = 4; // 0x4
+ field public static final int ERROR_KEYMINT_FAILURE = 10; // 0xa
+ field public static final int ERROR_KEYSTORE_FAILURE = 11; // 0xb
+ field public static final int ERROR_KEYSTORE_UNINITIALIZED = 3; // 0x3
+ field public static final int ERROR_KEY_CORRUPTED = 7; // 0x7
+ field public static final int ERROR_KEY_DOES_NOT_EXIST = 6; // 0x6
+ field public static final int ERROR_KEY_NOT_TEMPORALLY_VALID = 14; // 0xe
+ field public static final int ERROR_KEY_OPERATION_EXPIRED = 15; // 0xf
+ field public static final int ERROR_OTHER = 1; // 0x1
+ field public static final int ERROR_PERMISSION_DENIED = 5; // 0x5
+ field public static final int ERROR_UNIMPLEMENTED = 12; // 0xc
+ field public static final int ERROR_USER_AUTHENTICATION_REQUIRED = 2; // 0x2
+ }
+
@Deprecated public final class KeyStoreParameter implements java.security.KeyStore.ProtectionParameter {
method @Deprecated public boolean isEncryptionRequired();
}
@@ -38112,9 +38743,11 @@
public abstract class CarrierService extends android.app.Service {
ctor public CarrierService();
- method public final void notifyCarrierNetworkChange(boolean);
+ method @Deprecated public final void notifyCarrierNetworkChange(boolean);
+ method public final void notifyCarrierNetworkChange(int, boolean);
method @CallSuper public android.os.IBinder onBind(android.content.Intent);
- method public abstract android.os.PersistableBundle onLoadConfig(android.service.carrier.CarrierIdentifier);
+ method @Deprecated public abstract android.os.PersistableBundle onLoadConfig(android.service.carrier.CarrierIdentifier);
+ method @Nullable public android.os.PersistableBundle onLoadConfig(int, @Nullable android.service.carrier.CarrierIdentifier);
field public static final String CARRIER_SERVICE_INTERFACE = "android.service.carrier.CarrierService";
}
@@ -38169,6 +38802,7 @@
method @NonNull public CharSequence getSubtitle();
method @NonNull public CharSequence getTitle();
method @Nullable public CharSequence getZone();
+ method public boolean isAuthRequired();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.service.controls.Control> CREATOR;
field public static final int STATUS_DISABLED = 4; // 0x4
@@ -38183,6 +38817,7 @@
ctor public Control.StatefulBuilder(@NonNull android.service.controls.Control);
method @NonNull public android.service.controls.Control build();
method @NonNull public android.service.controls.Control.StatefulBuilder setAppIntent(@NonNull android.app.PendingIntent);
+ method @NonNull public android.service.controls.Control.StatefulBuilder setAuthRequired(boolean);
method @NonNull public android.service.controls.Control.StatefulBuilder setControlId(@NonNull String);
method @NonNull public android.service.controls.Control.StatefulBuilder setControlTemplate(@NonNull android.service.controls.templates.ControlTemplate);
method @NonNull public android.service.controls.Control.StatefulBuilder setCustomColor(@Nullable android.content.res.ColorStateList);
@@ -38944,6 +39579,13 @@
package android.service.voice {
+ public final class VisibleActivityInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.service.voice.VoiceInteractionSession.ActivityId getActivityId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.VisibleActivityInfo> CREATOR;
+ }
+
public class VoiceInteractionService extends android.app.Service {
ctor public VoiceInteractionService();
method public int getDisabledShowContext();
@@ -39005,6 +39647,7 @@
method public void onTaskStarted(android.content.Intent, int);
method public void onTrimMemory(int);
method public final void performDirectAction(@NonNull android.app.DirectAction, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.os.Bundle>);
+ method public final void registerVisibleActivityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.service.voice.VoiceInteractionSession.VisibleActivityCallback);
method public final void requestDirectActions(@NonNull android.service.voice.VoiceInteractionSession.ActivityId, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.DirectAction>>);
method public void setContentView(android.view.View);
method public void setDisabledShowContext(int);
@@ -39014,6 +39657,7 @@
method public void show(android.os.Bundle, int);
method public void startAssistantActivity(android.content.Intent);
method public void startVoiceActivity(android.content.Intent);
+ method public final void unregisterVisibleActivityCallback(@NonNull android.service.voice.VoiceInteractionSession.VisibleActivityCallback);
field public static final int SHOW_SOURCE_ACTIVITY = 16; // 0x10
field public static final int SHOW_SOURCE_APPLICATION = 8; // 0x8
field public static final int SHOW_SOURCE_ASSIST_GESTURE = 4; // 0x4
@@ -39087,6 +39731,11 @@
method public boolean isActive();
}
+ public static interface VoiceInteractionSession.VisibleActivityCallback {
+ method public default void onInvisible(@NonNull android.service.voice.VoiceInteractionSession.ActivityId);
+ method public default void onVisible(@NonNull android.service.voice.VisibleActivityInfo);
+ }
+
public abstract class VoiceInteractionSessionService extends android.app.Service {
ctor public VoiceInteractionSessionService();
method public android.os.IBinder onBind(android.content.Intent);
@@ -39581,6 +40230,7 @@
field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80
field public static final int CAPABILITY_MERGE_CONFERENCE = 4; // 0x4
field public static final int CAPABILITY_MUTE = 64; // 0x40
+ field public static final int CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT = 268435456; // 0x10000000
field public static final int CAPABILITY_RESPOND_VIA_TEXT = 32; // 0x20
field public static final int CAPABILITY_SEPARATE_FROM_CONFERENCE = 4096; // 0x1000
field public static final int CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL = 768; // 0x300
@@ -39646,6 +40296,7 @@
method public final void cancelCall();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onPlaceCall(@NonNull android.net.Uri, @NonNull android.telecom.PhoneAccountHandle, boolean);
+ method public void onRedirectionTimeout();
method public final boolean onUnbind(@NonNull android.content.Intent);
method public final void placeCallUnmodified();
method public final void redirectCall(@NonNull android.net.Uri, @NonNull android.telecom.PhoneAccountHandle, boolean);
@@ -39866,6 +40517,7 @@
field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80
field public static final int CAPABILITY_MERGE_CONFERENCE = 4; // 0x4
field public static final int CAPABILITY_MUTE = 64; // 0x40
+ field public static final int CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT = 536870912; // 0x20000000
field public static final int CAPABILITY_RESPOND_VIA_TEXT = 32; // 0x20
field public static final int CAPABILITY_SEPARATE_FROM_CONFERENCE = 4096; // 0x1000
field public static final int CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL = 768; // 0x300
@@ -39897,6 +40549,7 @@
field public static final String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS";
field public static final String EXTRA_IS_RTT_AUDIO_PRESENT = "android.telecom.extra.IS_RTT_AUDIO_PRESENT";
field public static final String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
+ field public static final String EXTRA_LAST_KNOWN_CELL_IDENTITY = "android.telecom.extra.LAST_KNOWN_CELL_IDENTITY";
field public static final String EXTRA_SIP_INVITE = "android.telecom.extra.SIP_INVITE";
field public static final int PROPERTY_ASSISTED_DIALING = 512; // 0x200
field public static final int PROPERTY_CROSS_SIM = 8192; // 0x2000
@@ -40332,7 +40985,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts();
method public String getDefaultDialerPackage();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(String);
- method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle);
+ method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle);
method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
method public android.telecom.PhoneAccountHandle getSimCallManager();
@@ -40644,6 +41297,7 @@
method @NonNull public java.util.List<java.lang.Integer> getBands();
method @NonNull public java.util.List<java.lang.String> getMccMncs();
method public int getPriority();
+ method @NonNull public java.util.List<android.telephony.RadioAccessSpecifier> getRadioAccessSpecifiers();
method public int getSubId();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.AvailableNetworkInfo> CREATOR;
@@ -40652,6 +41306,14 @@
field public static final int PRIORITY_MED = 2; // 0x2
}
+ public static final class AvailableNetworkInfo.Builder {
+ ctor public AvailableNetworkInfo.Builder(int);
+ method @NonNull public android.telephony.AvailableNetworkInfo build();
+ method @NonNull public android.telephony.AvailableNetworkInfo.Builder setMccMncs(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.telephony.AvailableNetworkInfo.Builder setPriority(int);
+ method @NonNull public android.telephony.AvailableNetworkInfo.Builder setRadioAccessSpecifiers(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>);
+ }
+
public final class BarringInfo implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.telephony.BarringInfo.BarringServiceInfo getBarringServiceInfo(int);
@@ -40685,11 +41347,11 @@
}
public class CarrierConfigManager {
- method @Nullable public android.os.PersistableBundle getConfig();
- method @Nullable public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int);
- method @Nullable public android.os.PersistableBundle getConfigForSubId(int);
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfig();
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int);
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfigForSubId(int);
method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle);
- method public void notifyConfigChangedForSubId(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyConfigChangedForSubId(int);
field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
field public static final int CARRIER_NR_AVAILABILITY_NSA = 1; // 0x1
field public static final int CARRIER_NR_AVAILABILITY_SA = 2; // 0x2
@@ -40760,6 +41422,7 @@
field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool";
field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
+ field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool";
field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
field public static final String KEY_CARRIER_USSD_METHOD_INT = "carrier_ussd_method_int";
@@ -40779,6 +41442,7 @@
field public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
field public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
field public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
+ field public static final String KEY_CELLULAR_USAGE_SETTING_INT = "cellular_usage_setting_int";
field public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = "check_pricing_with_carrier_data_roaming_bool";
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
@@ -40818,9 +41482,12 @@
field public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL = "editable_wfc_roaming_mode_bool";
field public static final String KEY_EMERGENCY_NOTIFICATION_DELAY_INT = "emergency_notification_delay_int";
field public static final String KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY = "emergency_number_prefix_string_array";
+ field public static final String KEY_ENABLE_CROSS_SIM_CALLING_ON_OPPORTUNISTIC_DATA_BOOL = "enable_cross_sim_calling_on_opportunistic_data_bool";
field public static final String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
field public static final String KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL = "enhanced_4g_lte_on_by_default_bool";
field public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT = "enhanced_4g_lte_title_variant_int";
+ field public static final String KEY_ESIM_DOWNLOAD_RETRY_BACKOFF_TIMER_SEC_INT = "esim_download_retry_backoff_timer_sec_int";
+ field public static final String KEY_ESIM_MAX_DOWNLOAD_RETRY_ATTEMPTS_INT = "esim_max_download_retry_attempts_int";
field public static final String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
field public static final String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
field public static final String KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array";
@@ -40923,6 +41590,7 @@
field public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL = "show_wfc_location_privacy_policy_bool";
field public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
+ field public static final String KEY_SMDP_SERVER_ADDRESS_STRING = "smdp_server_address_string";
field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool";
field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL = "supports_device_to_device_communication_using_dtmf_bool";
@@ -42226,8 +42894,11 @@
field public static final String MMS_CONFIG_UA_PROF_URL = "uaProfUrl";
field public static final String MMS_CONFIG_USER_AGENT = "userAgent";
field public static final int MMS_ERROR_CONFIGURATION_ERROR = 7; // 0x7
+ field public static final int MMS_ERROR_DATA_DISABLED = 11; // 0xb
field public static final int MMS_ERROR_HTTP_FAILURE = 4; // 0x4
+ field public static final int MMS_ERROR_INACTIVE_SUBSCRIPTION = 10; // 0xa
field public static final int MMS_ERROR_INVALID_APN = 2; // 0x2
+ field public static final int MMS_ERROR_INVALID_SUBSCRIPTION_ID = 9; // 0x9
field public static final int MMS_ERROR_IO_ERROR = 5; // 0x5
field public static final int MMS_ERROR_NO_DATA_NETWORK = 8; // 0x8
field public static final int MMS_ERROR_RETRY = 6; // 0x6
@@ -42384,10 +43055,12 @@
method @Nullable public String getMccString();
method @Deprecated public int getMnc();
method @Nullable public String getMncString();
- method public String getNumber();
+ method @Deprecated public String getNumber();
+ method public int getPortIndex();
method public int getSimSlotIndex();
method public int getSubscriptionId();
method public int getSubscriptionType();
+ method public int getUsageSetting();
method public boolean isEmbedded();
method public boolean isOpportunistic();
method public void writeToParcel(android.os.Parcel, int);
@@ -42417,6 +43090,8 @@
method @NonNull public java.util.List<android.net.Uri> getDeviceToDeviceStatusSharingContacts(int);
method public int getDeviceToDeviceStatusSharingPreference(int);
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getOpportunisticSubscriptions();
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public String getPhoneNumber(int, int);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public String getPhoneNumber(int);
method public static int getSlotIndex(int);
method @Nullable public int[] getSubscriptionIds(int);
method @NonNull public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int);
@@ -42428,6 +43103,7 @@
method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void removeSubscriptionsFromGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
+ method @RequiresPermission("carrier privileges") public void setCarrierPhoneNumber(int, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingContacts(int, @NonNull java.util.List<android.net.Uri>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingPreference(int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunistic(boolean, int);
@@ -42454,8 +43130,15 @@
field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
field public static final int INVALID_SIM_SLOT_INDEX = -1; // 0xffffffff
field public static final int INVALID_SUBSCRIPTION_ID = -1; // 0xffffffff
+ field public static final int PHONE_NUMBER_SOURCE_CARRIER = 2; // 0x2
+ field public static final int PHONE_NUMBER_SOURCE_IMS = 3; // 0x3
+ field public static final int PHONE_NUMBER_SOURCE_UICC = 1; // 0x1
field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0
field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1
+ field public static final int USAGE_SETTING_DATA_CENTRIC = 2; // 0x2
+ field public static final int USAGE_SETTING_DEFAULT = 0; // 0x0
+ field public static final int USAGE_SETTING_UNKNOWN = -1; // 0xffffffff
+ field public static final int USAGE_SETTING_VOICE_CENTRIC = 1; // 0x1
}
public static class SubscriptionManager.OnOpportunisticSubscriptionsChangedListener {
@@ -42619,11 +43302,11 @@
method public int getCarrierIdFromSimMccMnc();
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public android.telephony.CellLocation getCellLocation();
method public int getDataActivity();
- method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getDataNetworkType();
+ method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public int getDataNetworkType();
method public int getDataState();
method @Deprecated @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getDeviceId();
method @Deprecated @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getDeviceId(int);
- method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getDeviceSoftwareVersion();
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public String getDeviceSoftwareVersion();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getEmergencyNumberList();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getEmergencyNumberList(int);
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<java.lang.String> getEquivalentHomePlmns();
@@ -42632,7 +43315,7 @@
method public String getIccAuthentication(int, int, String);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getImei();
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getImei(int);
- method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}) public String getLine1Number();
+ method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}) public String getLine1Number();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public String getManualNetworkSelectionPlmn();
method @Nullable public String getManufacturerCode();
method @Nullable public String getManufacturerCode(int);
@@ -42655,6 +43338,7 @@
method public int getPhoneType();
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
+ method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState(boolean, boolean);
method @Nullable public android.telephony.SignalStrength getSignalStrength();
method public int getSimCarrierId();
method @Nullable public CharSequence getSimCarrierIdName();
@@ -42676,26 +43360,26 @@
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVisualVoicemailPackageName();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailAlphaTag();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber();
- method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getVoiceNetworkType();
+ method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public int getVoiceNetworkType();
method @Nullable public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle);
method public boolean hasCarrierPrivileges();
method public boolean hasIccCard();
- method @Deprecated public boolean iccCloseLogicalChannel(int);
- method @Deprecated public byte[] iccExchangeSimIO(int, int, int, int, int, String);
+ method public boolean iccCloseLogicalChannel(int);
+ method public byte[] iccExchangeSimIO(int, int, int, int, int, String);
method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String);
- method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int);
- method @Deprecated public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
- method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
+ method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int);
+ method public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
+ method public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
method public boolean isConcurrentVoiceAndDataSupported();
method public boolean isDataCapable();
- method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isDataConnectionAllowed();
- method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabled();
- method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabledForReason(int);
- method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled();
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataConnectionAllowed();
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabled();
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabledForReason(int);
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataRoamingEnabled();
method public boolean isEmergencyNumber(@NonNull String);
method public boolean isHearingAidCompatibilitySupported();
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isManualNetworkSelectionAllowed();
- method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isModemEnabledForSlot(int);
+ method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isModemEnabledForSlot(int);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int isMultiSimSupported();
method public boolean isNetworkRoaming();
method public boolean isRadioInterfaceCapabilitySupported(@NonNull String);
@@ -42707,17 +43391,19 @@
method public boolean isWorldPhone();
method @Deprecated public void listen(android.telephony.PhoneStateListener, int);
method public void registerTelephonyCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback);
+ method public void registerTelephonyCallback(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback);
+ method @Nullable @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(boolean, @NonNull android.telephony.NetworkScanRequest, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyScanManager.NetworkScanCallback);
method public void sendDialerSpecialCode(String);
- method @Deprecated public String sendEnvelopeWithStatus(String);
+ method public String sendEnvelopeWithStatus(String);
method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
method public void sendVisualVoicemailSms(String, int, String, android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallComposerStatus(int);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabledForReason(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setForbiddenPlmns(@NonNull java.util.List<java.lang.String>);
- method public boolean setLine1NumberForDisplay(String, String);
+ method @Deprecated public boolean setLine1NumberForDisplay(String, String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setNetworkSelectionModeAutomatic();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setNetworkSelectionModeManual(String, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setNetworkSelectionModeManual(@NonNull String, boolean, int);
@@ -42777,11 +43463,15 @@
field public static final int DATA_DISCONNECTED = 0; // 0x0
field public static final int DATA_DISCONNECTING = 4; // 0x4
field public static final int DATA_ENABLED_REASON_CARRIER = 2; // 0x2
+ field public static final int DATA_ENABLED_REASON_OVERRIDE = 4; // 0x4
field public static final int DATA_ENABLED_REASON_POLICY = 1; // 0x1
field public static final int DATA_ENABLED_REASON_THERMAL = 3; // 0x3
+ field public static final int DATA_ENABLED_REASON_UNKNOWN = -1; // 0xffffffff
field public static final int DATA_ENABLED_REASON_USER = 0; // 0x0
+ field public static final int DATA_HANDOVER_IN_PROGRESS = 5; // 0x5
field public static final int DATA_SUSPENDED = 3; // 0x3
field public static final int DATA_UNKNOWN = -1; // 0xffffffff
+ field public static final int DEFAULT_PORT_INDEX = 0; // 0x0
field public static final int ERI_FLASH = 2; // 0x2
field public static final int ERI_OFF = 1; // 0x1
field public static final int ERI_ON = 0; // 0x0
@@ -42928,14 +43618,28 @@
method public int describeContents();
method public int getCardId();
method @Nullable public String getEid();
- method @Nullable public String getIccId();
- method public int getSlotIndex();
+ method @Deprecated @Nullable public String getIccId();
+ method public int getPhysicalSlotIndex();
+ method @NonNull public java.util.Collection<android.telephony.UiccPortInfo> getPorts();
+ method @Deprecated public int getSlotIndex();
method public boolean isEuicc();
+ method public boolean isMultipleEnabledProfilesSupported();
method public boolean isRemovable();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.UiccCardInfo> CREATOR;
}
+ public final class UiccPortInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public String getIccId();
+ method @IntRange(from=0) public int getLogicalSlotIndex();
+ method @IntRange(from=0) public int getPortIndex();
+ method public boolean isActive();
+ method public void writeToParcel(@Nullable android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.UiccPortInfo> CREATOR;
+ field public static final String ICCID_REDACTED = "FFFFFFFFFFFFFFFFFFFF";
+ }
+
public abstract class VisualVoicemailService extends android.app.Service {
ctor public VisualVoicemailService();
method public android.os.IBinder onBind(android.content.Intent);
@@ -43014,6 +43718,8 @@
method public String getMmsProxyAddressAsString();
method public int getMmsProxyPort();
method public android.net.Uri getMmsc();
+ method public int getMtuV4();
+ method public int getMtuV6();
method public int getMvnoType();
method public int getNetworkTypeBitmask();
method public String getOperatorNumeric();
@@ -43070,10 +43776,14 @@
method @NonNull public android.telephony.data.ApnSetting.Builder setMmsProxyAddress(@Nullable String);
method @NonNull public android.telephony.data.ApnSetting.Builder setMmsProxyPort(int);
method @NonNull public android.telephony.data.ApnSetting.Builder setMmsc(@Nullable android.net.Uri);
+ method @NonNull public android.telephony.data.ApnSetting.Builder setMtuV4(int);
+ method @NonNull public android.telephony.data.ApnSetting.Builder setMtuV6(int);
method @NonNull public android.telephony.data.ApnSetting.Builder setMvnoType(int);
method @NonNull public android.telephony.data.ApnSetting.Builder setNetworkTypeBitmask(int);
method @NonNull public android.telephony.data.ApnSetting.Builder setOperatorNumeric(@Nullable String);
method @NonNull public android.telephony.data.ApnSetting.Builder setPassword(@Nullable String);
+ method @NonNull public android.telephony.data.ApnSetting.Builder setPersistent(boolean);
+ method @NonNull public android.telephony.data.ApnSetting.Builder setProfileId(int);
method @NonNull public android.telephony.data.ApnSetting.Builder setProtocol(int);
method @Deprecated public android.telephony.data.ApnSetting.Builder setProxyAddress(java.net.InetAddress);
method @NonNull public android.telephony.data.ApnSetting.Builder setProxyAddress(@Nullable String);
@@ -43236,8 +43946,10 @@
method @Nullable public String getEid();
method @Nullable public android.telephony.euicc.EuiccInfo getEuiccInfo();
method public boolean isEnabled();
+ method public boolean isSimPortAvailable(int);
method public void startResolutionActivity(android.app.Activity, int, android.content.Intent, android.app.PendingIntent) throws android.content.IntentSender.SendIntentException;
- method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, android.app.PendingIntent);
+ method @Deprecated @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, android.app.PendingIntent);
+ method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, int, @NonNull android.app.PendingIntent);
method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void updateSubscriptionNickname(int, @Nullable String, @NonNull android.app.PendingIntent);
field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
field public static final String ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE = "android.telephony.euicc.action.NOTIFY_CARRIER_SETUP_INCOMPLETE";
@@ -43401,8 +44113,10 @@
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public boolean isVoWiFiSettingEnabled();
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public boolean isVtSettingEnabled();
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
+ method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public void registerImsStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsStateCallback) throws android.telephony.ims.ImsException;
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback) throws android.telephony.ims.ImsException;
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
+ method public void unregisterImsStateCallback(@NonNull android.telephony.ims.ImsStateCallback);
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public void unregisterMmTelCapabilityCallback(@NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback);
field public static final int WIFI_MODE_CELLULAR_PREFERRED = 1; // 0x1
field public static final int WIFI_MODE_WIFI_ONLY = 0; // 0x0
@@ -43419,7 +44133,9 @@
method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @NonNull public android.telephony.ims.RcsUceAdapter getUceAdapter();
method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
+ method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"}) public void registerImsStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsStateCallback) throws android.telephony.ims.ImsException;
method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
+ method public void unregisterImsStateCallback(@NonNull android.telephony.ims.ImsStateCallback);
field public static final String ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN = "android.telephony.ims.action.SHOW_CAPABILITY_DISCOVERY_OPT_IN";
}
@@ -43618,6 +44334,19 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsRegistrationAttributes> CREATOR;
}
+ public abstract class ImsStateCallback {
+ ctor public ImsStateCallback();
+ method public abstract void onAvailable();
+ method public abstract void onError();
+ method public abstract void onUnavailable(int);
+ field public static final int REASON_IMS_SERVICE_DISCONNECTED = 3; // 0x3
+ field public static final int REASON_IMS_SERVICE_NOT_READY = 6; // 0x6
+ field public static final int REASON_NO_IMS_SERVICE_CONFIGURED = 4; // 0x4
+ field public static final int REASON_SUBSCRIPTION_INACTIVE = 5; // 0x5
+ field public static final int REASON_UNKNOWN_PERMANENT_ERROR = 2; // 0x2
+ field public static final int REASON_UNKNOWN_TEMPORARY_ERROR = 1; // 0x1
+ }
+
public class RcsUceAdapter {
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isUceSettingEnabled() throws android.telephony.ims.ImsException;
}
@@ -44072,6 +44801,7 @@
field public static final int TYPE_TEXT_FLAG_CAP_CHARACTERS = 4096; // 0x1000
field public static final int TYPE_TEXT_FLAG_CAP_SENTENCES = 16384; // 0x4000
field public static final int TYPE_TEXT_FLAG_CAP_WORDS = 8192; // 0x2000
+ field public static final int TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS = 1048576; // 0x100000
field public static final int TYPE_TEXT_FLAG_IME_MULTI_LINE = 262144; // 0x40000
field public static final int TYPE_TEXT_FLAG_MULTI_LINE = 131072; // 0x20000
field public static final int TYPE_TEXT_FLAG_NO_SUGGESTIONS = 524288; // 0x80000
@@ -44150,8 +44880,10 @@
field public static final int DIR_LEFT_TO_RIGHT = 1; // 0x1
field public static final int DIR_RIGHT_TO_LEFT = -1; // 0xffffffff
field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2
+ field public static final int HYPHENATION_FREQUENCY_FULL_FAST = 4; // 0x4
field public static final int HYPHENATION_FREQUENCY_NONE = 0; // 0x0
field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1
+ field public static final int HYPHENATION_FREQUENCY_NORMAL_FAST = 3; // 0x3
field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
}
@@ -45199,8 +45931,10 @@
public class StyleSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan {
ctor public StyleSpan(int);
+ ctor public StyleSpan(int, int);
ctor public StyleSpan(@NonNull android.os.Parcel);
method public int describeContents();
+ method public int getFontWeightAdjustment();
method public int getSpanTypeId();
method public int getStyle();
method public void updateDrawState(android.text.TextPaint);
@@ -45218,6 +45952,17 @@
method public void writeToParcel(android.os.Parcel, int);
}
+ public final class SuggestionRangeSpan extends android.text.style.CharacterStyle implements android.text.ParcelableSpan {
+ ctor public SuggestionRangeSpan();
+ method public int describeContents();
+ method public int getBackgroundColor();
+ method public int getSpanTypeId();
+ method public void setBackgroundColor(int);
+ method public void updateDrawState(@NonNull android.text.TextPaint);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.text.style.SuggestionRangeSpan> CREATOR;
+ }
+
public class SuggestionSpan extends android.text.style.CharacterStyle implements android.text.ParcelableSpan {
ctor public SuggestionSpan(android.content.Context, String[], int);
ctor public SuggestionSpan(java.util.Locale, String[], int);
@@ -45569,7 +46314,7 @@
method public static final boolean addLinks(@NonNull android.text.Spannable, @NonNull java.util.regex.Pattern, @Nullable String, @Nullable android.text.util.Linkify.MatchFilter, @Nullable android.text.util.Linkify.TransformFilter);
method public static final boolean addLinks(@NonNull android.text.Spannable, @NonNull java.util.regex.Pattern, @Nullable String, @Nullable String[], @Nullable android.text.util.Linkify.MatchFilter, @Nullable android.text.util.Linkify.TransformFilter);
method public static final boolean addLinks(@NonNull android.text.Spannable, @NonNull java.util.regex.Pattern, @Nullable String, @Nullable String[], @Nullable android.text.util.Linkify.MatchFilter, @Nullable android.text.util.Linkify.TransformFilter, @Nullable java.util.function.Function<java.lang.String,android.text.style.URLSpan>);
- field public static final int ALL = 15; // 0xf
+ field @Deprecated public static final int ALL = 15; // 0xf
field public static final int EMAIL_ADDRESSES = 2; // 0x2
field @Deprecated public static final int MAP_ADDRESSES = 8; // 0x8
field public static final int PHONE_NUMBERS = 4; // 0x4
@@ -45955,6 +46700,7 @@
method public boolean contains(Object);
method public boolean containsAll(java.util.Collection<?>);
method public void ensureCapacity(int);
+ method public void forEach(java.util.function.Consumer<? super E>);
method public int indexOf(Object);
method public boolean isEmpty();
method public java.util.Iterator<E> iterator();
@@ -46834,8 +47580,15 @@
}
@UiThread public interface AttachedSurfaceControl {
+ method public default void addOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
method public boolean applyTransactionOnDraw(@NonNull android.view.SurfaceControl.Transaction);
method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl);
+ method public default int getBufferTransformHint();
+ method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
+ }
+
+ @UiThread public static interface AttachedSurfaceControl.OnBufferTransformHintChangedListener {
+ method public void onBufferTransformHintChanged(int);
}
public final class Choreographer {
@@ -47471,6 +48224,10 @@
field public static final int KEYCODE_CUT = 277; // 0x115
field public static final int KEYCODE_D = 32; // 0x20
field public static final int KEYCODE_DEL = 67; // 0x43
+ field public static final int KEYCODE_DEMO_APP_1 = 301; // 0x12d
+ field public static final int KEYCODE_DEMO_APP_2 = 302; // 0x12e
+ field public static final int KEYCODE_DEMO_APP_3 = 303; // 0x12f
+ field public static final int KEYCODE_DEMO_APP_4 = 304; // 0x130
field public static final int KEYCODE_DPAD_CENTER = 23; // 0x17
field public static final int KEYCODE_DPAD_DOWN = 20; // 0x14
field public static final int KEYCODE_DPAD_DOWN_LEFT = 269; // 0x10d
@@ -47502,6 +48259,10 @@
field public static final int KEYCODE_F7 = 137; // 0x89
field public static final int KEYCODE_F8 = 138; // 0x8a
field public static final int KEYCODE_F9 = 139; // 0x8b
+ field public static final int KEYCODE_FEATURED_APP_1 = 297; // 0x129
+ field public static final int KEYCODE_FEATURED_APP_2 = 298; // 0x12a
+ field public static final int KEYCODE_FEATURED_APP_3 = 299; // 0x12b
+ field public static final int KEYCODE_FEATURED_APP_4 = 300; // 0x12c
field public static final int KEYCODE_FOCUS = 80; // 0x50
field public static final int KEYCODE_FORWARD = 125; // 0x7d
field public static final int KEYCODE_FORWARD_DEL = 112; // 0x70
@@ -47667,6 +48428,14 @@
field public static final int KEYCODE_U = 49; // 0x31
field public static final int KEYCODE_UNKNOWN = 0; // 0x0
field public static final int KEYCODE_V = 50; // 0x32
+ field public static final int KEYCODE_VIDEO_APP_1 = 289; // 0x121
+ field public static final int KEYCODE_VIDEO_APP_2 = 290; // 0x122
+ field public static final int KEYCODE_VIDEO_APP_3 = 291; // 0x123
+ field public static final int KEYCODE_VIDEO_APP_4 = 292; // 0x124
+ field public static final int KEYCODE_VIDEO_APP_5 = 293; // 0x125
+ field public static final int KEYCODE_VIDEO_APP_6 = 294; // 0x126
+ field public static final int KEYCODE_VIDEO_APP_7 = 295; // 0x127
+ field public static final int KEYCODE_VIDEO_APP_8 = 296; // 0x128
field public static final int KEYCODE_VOICE_ASSIST = 231; // 0xe7
field public static final int KEYCODE_VOLUME_DOWN = 25; // 0x19
field public static final int KEYCODE_VOLUME_MUTE = 164; // 0xa4
@@ -48325,6 +49094,12 @@
method public void readFromParcel(android.os.Parcel);
method public void release();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int BUFFER_TRANSFORM_IDENTITY = 0; // 0x0
+ field public static final int BUFFER_TRANSFORM_MIRROR_HORIZONTAL = 1; // 0x1
+ field public static final int BUFFER_TRANSFORM_MIRROR_VERTICAL = 2; // 0x2
+ field public static final int BUFFER_TRANSFORM_ROTATE_180 = 3; // 0x3
+ field public static final int BUFFER_TRANSFORM_ROTATE_270 = 7; // 0x7
+ field public static final int BUFFER_TRANSFORM_ROTATE_90 = 4; // 0x4
field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl> CREATOR;
}
@@ -48333,6 +49108,7 @@
method @NonNull public android.view.SurfaceControl build();
method @NonNull public android.view.SurfaceControl.Builder setBufferSize(@IntRange(from=0) int, @IntRange(from=0) int);
method @NonNull public android.view.SurfaceControl.Builder setFormat(int);
+ method @NonNull public android.view.SurfaceControl.Builder setHidden(boolean);
method @NonNull public android.view.SurfaceControl.Builder setName(@NonNull String);
method @NonNull public android.view.SurfaceControl.Builder setOpaque(boolean);
method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl);
@@ -48340,17 +49116,25 @@
public static class SurfaceControl.Transaction implements java.io.Closeable android.os.Parcelable {
ctor public SurfaceControl.Transaction();
+ method @NonNull public android.view.SurfaceControl.Transaction addTransactionCommittedListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.TransactionCommittedListener);
method public void apply();
method public void close();
method public int describeContents();
method @NonNull public android.view.SurfaceControl.Transaction merge(@NonNull android.view.SurfaceControl.Transaction);
method @NonNull public android.view.SurfaceControl.Transaction reparent(@NonNull android.view.SurfaceControl, @Nullable android.view.SurfaceControl);
method @NonNull public android.view.SurfaceControl.Transaction setAlpha(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0, to=1.0) float);
+ method @NonNull public android.view.SurfaceControl.Transaction setBuffer(@NonNull android.view.SurfaceControl, @Nullable android.hardware.HardwareBuffer);
method @NonNull public android.view.SurfaceControl.Transaction setBufferSize(@NonNull android.view.SurfaceControl, @IntRange(from=0) int, @IntRange(from=0) int);
+ method @NonNull public android.view.SurfaceControl.Transaction setBufferTransform(@NonNull android.view.SurfaceControl, int);
+ method @NonNull public android.view.SurfaceControl.Transaction setCrop(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect);
+ method @NonNull public android.view.SurfaceControl.Transaction setDamageRegion(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Region);
method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int);
method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int, int);
method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
+ method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean);
+ method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float);
+ method @NonNull public android.view.SurfaceControl.Transaction setScale(@NonNull android.view.SurfaceControl, float, float);
method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl.Transaction> CREATOR;
@@ -48466,6 +49250,10 @@
field public static final int TO_RIGHT = 8; // 0x8
}
+ public interface TransactionCommittedListener {
+ method public void onTransactionCommitted();
+ }
+
public final class VelocityTracker {
method public void addMovement(android.view.MotionEvent);
method public void clear();
@@ -49187,6 +49975,7 @@
field public static final int AUTOFILL_TYPE_NONE = 0; // 0x0
field public static final int AUTOFILL_TYPE_TEXT = 1; // 0x1
field public static final int AUTOFILL_TYPE_TOGGLE = 2; // 0x2
+ field public static final int DRAG_FLAG_ACCESSIBILITY_ACTION = 1024; // 0x400
field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
@@ -50602,9 +51391,9 @@
method public int getRecordCount();
method public int getWindowChanges();
method public void initFromParcel(android.os.Parcel);
- method public static android.view.accessibility.AccessibilityEvent obtain(int);
- method public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent);
- method public static android.view.accessibility.AccessibilityEvent obtain();
+ method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(int);
+ method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent);
+ method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain();
method public void setAction(int);
method public void setContentChangeTypes(int);
method public void setEventTime(long);
@@ -50613,6 +51402,9 @@
method public void setPackageName(CharSequence);
method public void writeToParcel(android.os.Parcel, int);
field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4
+ field public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 512; // 0x200
+ field public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 256; // 0x100
+ field public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 128; // 0x80
field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10
field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20
field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
@@ -50669,8 +51461,11 @@
public final class AccessibilityManager {
method public void addAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
+ method public void addAccessibilityServicesStateChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
+ method public void addAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
method public boolean addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
method public void addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, @Nullable android.os.Handler);
+ method public void addAudioDescriptionRequestedChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
method public boolean addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
method public void addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, @Nullable android.os.Handler);
method @ColorInt public int getAccessibilityFocusColor();
@@ -50681,10 +51476,13 @@
method public int getRecommendedTimeoutMillis(int, int);
method public void interrupt();
method public static boolean isAccessibilityButtonSupported();
+ method public boolean isAudioDescriptionRequested();
method public boolean isEnabled();
method public boolean isTouchExplorationEnabled();
method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
+ method public boolean removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
method public boolean removeAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
+ method public boolean removeAudioDescriptionRequestedChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
method public boolean removeTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
field public static final int FLAG_CONTENT_CONTROLS = 4; // 0x4
@@ -50692,10 +51490,18 @@
field public static final int FLAG_CONTENT_TEXT = 2; // 0x2
}
+ public static interface AccessibilityManager.AccessibilityServicesStateChangeListener {
+ method public void onAccessibilityServicesStateChanged(@NonNull android.view.accessibility.AccessibilityManager);
+ }
+
public static interface AccessibilityManager.AccessibilityStateChangeListener {
method public void onAccessibilityStateChanged(boolean);
}
+ public static interface AccessibilityManager.AudioDescriptionRequestedChangeListener {
+ method public void onAudioDescriptionRequestedChanged(boolean);
+ }
+
public static interface AccessibilityManager.TouchExplorationStateChangeListener {
method public void onTouchExplorationStateChanged(boolean);
}
@@ -50749,6 +51555,7 @@
method @Nullable public android.view.accessibility.AccessibilityNodeInfo.TouchDelegateInfo getTouchDelegateInfo();
method public android.view.accessibility.AccessibilityNodeInfo getTraversalAfter();
method public android.view.accessibility.AccessibilityNodeInfo getTraversalBefore();
+ method @Nullable public String getUniqueId();
method public String getViewIdResourceName();
method public android.view.accessibility.AccessibilityWindowInfo getWindow();
method public int getWindowId();
@@ -50774,13 +51581,13 @@
method public boolean isShowingHintText();
method public boolean isTextEntryKey();
method public boolean isVisibleToUser();
- method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View);
- method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int);
- method public static android.view.accessibility.AccessibilityNodeInfo obtain();
- method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.accessibility.AccessibilityNodeInfo);
+ method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View);
+ method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int);
+ method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain();
+ method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.accessibility.AccessibilityNodeInfo);
method public boolean performAction(int);
method public boolean performAction(int, android.os.Bundle);
- method public void recycle();
+ method @Deprecated public void recycle();
method public boolean refresh();
method public boolean refreshWithExtraData(String, android.os.Bundle);
method @Deprecated public void removeAction(int);
@@ -50843,6 +51650,7 @@
method public void setTraversalAfter(android.view.View, int);
method public void setTraversalBefore(android.view.View);
method public void setTraversalBefore(android.view.View, int);
+ method public void setUniqueId(@Nullable String);
method public void setViewIdResourceName(String);
method public void setVisibleToUser(boolean);
method public void writeToParcel(android.os.Parcel, int);
@@ -50911,6 +51719,9 @@
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_COPY;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_CUT;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DISMISS;
+ field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DRAG_CANCEL;
+ field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DRAG_DROP;
+ field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DRAG_START;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_EXPAND;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_FOCUS;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_HIDE_TOOLTIP;
@@ -50939,7 +51750,12 @@
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_SELECTION;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_TEXT;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_ON_SCREEN;
+ field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_SUGGESTIONS;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_TOOLTIP;
+ field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SWIPE_DOWN;
+ field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SWIPE_LEFT;
+ field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SWIPE_RIGHT;
+ field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SWIPE_UP;
field @NonNull public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> CREATOR;
}
@@ -50950,8 +51766,8 @@
method public int getRowCount();
method public int getSelectionMode();
method public boolean isHierarchical();
- method public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean);
- method public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean, int);
+ method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean);
+ method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean, int);
field public static final int SELECTION_MODE_MULTIPLE = 2; // 0x2
field public static final int SELECTION_MODE_NONE = 0; // 0x0
field public static final int SELECTION_MODE_SINGLE = 1; // 0x1
@@ -50962,12 +51778,28 @@
ctor public AccessibilityNodeInfo.CollectionItemInfo(int, int, int, int, boolean, boolean);
method public int getColumnIndex();
method public int getColumnSpan();
+ method @Nullable public String getColumnTitle();
method public int getRowIndex();
method public int getRowSpan();
+ method @Nullable public String getRowTitle();
method @Deprecated public boolean isHeading();
method public boolean isSelected();
- method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean);
- method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean, boolean);
+ method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean);
+ method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean, boolean);
+ method @Deprecated @NonNull public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(@Nullable String, int, int, @Nullable String, int, int, boolean, boolean);
+ }
+
+ public static final class AccessibilityNodeInfo.CollectionItemInfo.Builder {
+ ctor public AccessibilityNodeInfo.CollectionItemInfo.Builder();
+ method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo build();
+ method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setColumnIndex(int);
+ method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setColumnSpan(int);
+ method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setColumnTitle(@Nullable String);
+ method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setHeading(boolean);
+ method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setRowIndex(int);
+ method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setRowSpan(int);
+ method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setRowTitle(@Nullable String);
+ method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setSelected(boolean);
}
public static final class AccessibilityNodeInfo.ExtraRenderingInfo {
@@ -50982,7 +51814,7 @@
method public float getMax();
method public float getMin();
method public int getType();
- method public static android.view.accessibility.AccessibilityNodeInfo.RangeInfo obtain(int, float, float, float);
+ method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.RangeInfo obtain(int, float, float, float);
field public static final int RANGE_TYPE_FLOAT = 1; // 0x1
field public static final int RANGE_TYPE_INT = 0; // 0x0
field public static final int RANGE_TYPE_PERCENT = 2; // 0x2
@@ -51016,6 +51848,7 @@
method public CharSequence getClassName();
method public CharSequence getContentDescription();
method public int getCurrentItemIndex();
+ method public int getDisplayId();
method public int getFromIndex();
method public int getItemCount();
method public int getMaxScrollX();
@@ -51035,9 +51868,9 @@
method public boolean isFullScreen();
method public boolean isPassword();
method public boolean isScrollable();
- method public static android.view.accessibility.AccessibilityRecord obtain(android.view.accessibility.AccessibilityRecord);
- method public static android.view.accessibility.AccessibilityRecord obtain();
- method public void recycle();
+ method @Deprecated public static android.view.accessibility.AccessibilityRecord obtain(android.view.accessibility.AccessibilityRecord);
+ method @Deprecated public static android.view.accessibility.AccessibilityRecord obtain();
+ method @Deprecated public void recycle();
method public void setAddedCount(int);
method public void setBeforeText(CharSequence);
method public void setChecked(boolean);
@@ -51432,6 +52265,7 @@
public final class AutofillManager {
method public void cancel();
+ method public void clearAutofillRequestCallback();
method public void commit();
method public void disableAutofillServices();
method @Nullable public android.content.ComponentName getAutofillServiceComponentName();
@@ -51457,6 +52291,7 @@
method public void registerCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
method public void requestAutofill(@NonNull android.view.View);
method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect);
+ method public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback);
method public void setUserData(@Nullable android.service.autofill.UserData);
method public void unregisterCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
field public static final String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE";
@@ -51475,6 +52310,10 @@
field public static final int EVENT_INPUT_UNAVAILABLE = 3; // 0x3
}
+ public interface AutofillRequestCallback {
+ method public void onFillRequest(@Nullable android.view.inputmethod.InlineSuggestionsRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
+ }
+
public final class AutofillValue implements android.os.Parcelable {
method public int describeContents();
method public static android.view.autofill.AutofillValue forDate(long);
@@ -51845,10 +52684,12 @@
ctor public InlineSuggestionsRequest.Builder(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder addInlinePresentationSpecs(@NonNull android.widget.inline.InlinePresentationSpec);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest build();
+ method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setClientSupported(boolean);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setExtras(@NonNull android.os.Bundle);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlineTooltipPresentationSpec(@NonNull android.widget.inline.InlinePresentationSpec);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setMaxSuggestionCount(int);
+ method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setServiceSupported(boolean);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setSupportedLocales(@NonNull android.os.LocaleList);
}
@@ -51879,13 +52720,14 @@
method public boolean commitContent(@NonNull android.view.inputmethod.InputContentInfo, int, @Nullable android.os.Bundle);
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(CharSequence, int);
+ method public default boolean commitText(@NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
method public boolean deleteSurroundingText(int, int);
method public boolean deleteSurroundingTextInCodePoints(int, int);
method public boolean endBatchEdit();
method public boolean finishComposingText();
method public int getCursorCapsMode(int);
method public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int);
- method public android.os.Handler getHandler();
+ method @Nullable public android.os.Handler getHandler();
method public CharSequence getSelectedText(int);
method @Nullable public default android.view.inputmethod.SurroundingText getSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int);
method @Nullable public CharSequence getTextAfterCursor(@IntRange(from=0) int, int);
@@ -51898,9 +52740,12 @@
method public boolean requestCursorUpdates(int);
method public boolean sendKeyEvent(android.view.KeyEvent);
method public boolean setComposingRegion(int, int);
+ method public default boolean setComposingRegion(int, int, @Nullable android.view.inputmethod.TextAttribute);
method public boolean setComposingText(CharSequence, int);
+ method public default boolean setComposingText(@NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
method public default boolean setImeConsumesInput(boolean);
method public boolean setSelection(int, int);
+ method @Nullable public default android.view.inputmethod.TextSnapshot takeSnapshot();
field public static final int CURSOR_UPDATE_IMMEDIATE = 1; // 0x1
field public static final int CURSOR_UPDATE_MONITOR = 2; // 0x2
field public static final int GET_EXTRACTED_TEXT_MONITOR = 1; // 0x1
@@ -51992,6 +52837,7 @@
method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
method public CharSequence loadLabel(android.content.pm.PackageManager);
method public boolean shouldShowInInputMethodPicker();
+ method public boolean supportsStylusHandwriting();
method public boolean suppressesSpellChecker();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InputMethodInfo> CREATOR;
@@ -52010,6 +52856,7 @@
method public boolean hideSoftInputFromWindow(android.os.IBinder, int);
method public boolean hideSoftInputFromWindow(android.os.IBinder, int, android.os.ResultReceiver);
method @Deprecated public void hideStatusIcon(android.os.IBinder);
+ method public void invalidateInput(@NonNull android.view.View);
method public boolean isAcceptingText();
method public boolean isActive(android.view.View);
method public boolean isActive();
@@ -52113,6 +52960,31 @@
field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.SurroundingText> CREATOR;
}
+ public final class TextAttribute implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.os.PersistableBundle getExtras();
+ method @NonNull public java.util.List<java.lang.String> getTextConversionSuggestions();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.TextAttribute> CREATOR;
+ }
+
+ public static final class TextAttribute.Builder {
+ ctor public TextAttribute.Builder();
+ method @NonNull public android.view.inputmethod.TextAttribute build();
+ method @NonNull public android.view.inputmethod.TextAttribute.Builder setExtras(@NonNull android.os.PersistableBundle);
+ method @NonNull public android.view.inputmethod.TextAttribute.Builder setTextConversionSuggestions(@NonNull java.util.List<java.lang.String>);
+ }
+
+ public final class TextSnapshot {
+ ctor public TextSnapshot(@NonNull android.view.inputmethod.SurroundingText, @IntRange(from=0xffffffff) int, @IntRange(from=0xffffffff) int, int);
+ method @IntRange(from=0xffffffff) public int getCompositionEnd();
+ method @IntRange(from=0xffffffff) public int getCompositionStart();
+ method public int getCursorCapsMode();
+ method @IntRange(from=0xffffffff) public int getSelectionEnd();
+ method @IntRange(from=0xffffffff) public int getSelectionStart();
+ method @NonNull public android.view.inputmethod.SurroundingText getSurroundingText();
+ }
+
}
package android.view.inspector {
@@ -53275,7 +54147,6 @@
method public void onPermissionRequest(android.webkit.PermissionRequest);
method public void onPermissionRequestCanceled(android.webkit.PermissionRequest);
method public void onProgressChanged(android.webkit.WebView, int);
- method @Deprecated public void onReachedMaxAppCacheSize(long, long, android.webkit.WebStorage.QuotaUpdater);
method public void onReceivedIcon(android.webkit.WebView, android.graphics.Bitmap);
method public void onReceivedTitle(android.webkit.WebView, String);
method public void onReceivedTouchIconUrl(android.webkit.WebView, String, boolean);
@@ -53427,9 +54298,6 @@
method public abstract void setAllowFileAccess(boolean);
method @Deprecated public abstract void setAllowFileAccessFromFileURLs(boolean);
method @Deprecated public abstract void setAllowUniversalAccessFromFileURLs(boolean);
- method @Deprecated public abstract void setAppCacheEnabled(boolean);
- method @Deprecated public abstract void setAppCacheMaxSize(long);
- method @Deprecated public abstract void setAppCachePath(String);
method public abstract void setBlockNetworkImage(boolean);
method public abstract void setBlockNetworkLoads(boolean);
method public abstract void setBuiltInZoomControls(boolean);
@@ -56806,6 +57674,8 @@
method public void clearOnExitAnimationListener();
method public void setOnExitAnimationListener(@NonNull android.window.SplashScreen.OnExitAnimationListener);
method public void setSplashScreenTheme(@StyleRes int);
+ field public static final int SPLASH_SCREEN_STYLE_EMPTY = 0; // 0x0
+ field public static final int SPLASH_SCREEN_STYLE_ICON = 1; // 0x1
}
public static interface SplashScreen.OnExitAnimationListener {
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/test-current.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/test-current.txt
index 2eafa93..8724b53 100644
--- a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/test-current.txt
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/test-current.txt
@@ -26,6 +26,7 @@
field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
field public static final String MANAGE_TOAST_RATE_LIMITING = "android.permission.MANAGE_TOAST_RATE_LIMITING";
field public static final String MODIFY_REFRESH_RATE_SWITCHING_TYPE = "android.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE";
+ field public static final String MODIFY_USER_PREFERRED_DISPLAY_MODE = "android.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE";
field public static final String NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS";
field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
field public static final String OVERRIDE_DISPLAY_MODE_REQUESTS = "android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS";
@@ -41,6 +42,7 @@
field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
+ field public static final String WRITE_COMMUNAL_STATE = "android.permission.WRITE_COMMUNAL_STATE";
field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
@@ -57,6 +59,7 @@
public static final class R.bool {
field public static final int config_assistantOnTopOfDream = 17891333; // 0x1110005
field public static final int config_perDisplayFocusEnabled = 17891332; // 0x1110004
+ field public static final int config_preventImeStartupUnlessTextEditor;
field public static final int config_remoteInsetsControllerControlsSystemBars = 17891334; // 0x1110006
}
@@ -113,6 +116,7 @@
method @RequiresPermission(android.Manifest.permission.RESET_APP_ERRORS) public void resetAppErrors();
method public static void resumeAppSwitches() throws android.os.RemoteException;
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
@@ -127,6 +131,9 @@
field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0
field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4
field public static final int PROCESS_STATE_TOP = 2; // 0x2
+ field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff
+ field public static final int STOP_USER_ON_SWITCH_FALSE = 0; // 0x0
+ field public static final int STOP_USER_ON_SWITCH_TRUE = 1; // 0x1
}
public static class ActivityManager.RunningAppProcessInfo implements android.os.Parcelable {
@@ -140,11 +147,11 @@
}
public class ActivityOptions {
+ method @NonNull public static android.app.ActivityOptions fromBundle(@NonNull android.os.Bundle);
method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
method public static void setExitTransitionTimeout(long);
method public void setLaunchActivityType(int);
- method public void setLaunchTaskId(int);
method public void setLaunchWindowingMode(int);
method public void setLaunchedFromBubble(boolean);
method public void setTaskAlwaysOnTop(boolean);
@@ -242,12 +249,15 @@
public class BroadcastOptions {
ctor public BroadcastOptions(@NonNull android.os.Bundle);
- method public int getMaxManifestReceiverApiLevel();
+ method @Deprecated public int getMaxManifestReceiverApiLevel();
method public long getTemporaryAppAllowlistDuration();
method @Nullable public String getTemporaryAppAllowlistReason();
method public int getTemporaryAppAllowlistReasonCode();
method public int getTemporaryAppAllowlistType();
- method public void setMaxManifestReceiverApiLevel(int);
+ method @Deprecated public void setMaxManifestReceiverApiLevel(int);
+ method public boolean testRequireCompatChange(int);
+ field public static final long CHANGE_ALWAYS_DISABLED = 210856463L; // 0xc916a0fL
+ field public static final long CHANGE_ALWAYS_ENABLED = 209888056L; // 0xc82a338L
}
public class DownloadManager {
@@ -256,15 +266,12 @@
public class DreamManager {
method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isDreaming();
- method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void setActiveDream(@NonNull android.content.ComponentName);
+ method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void setActiveDream(@Nullable android.content.ComponentName);
+ method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void setDreamOverlay(@Nullable android.content.ComponentName);
method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void startDream(@NonNull android.content.ComponentName);
method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void stopDream();
}
- public final class GameManager {
- method @RequiresPermission("android.permission.MANAGE_GAME_MODE") public void setGameMode(@NonNull String, int);
- }
-
public abstract class HomeVisibilityListener {
ctor public HomeVisibilityListener();
method public abstract void onHomeVisibilityChanged(boolean);
@@ -275,13 +282,17 @@
method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"}) public boolean setLock(int, @Nullable byte[], int, @Nullable byte[]);
}
+ public class LocaleManager {
+ method @Nullable public android.os.LocaleList getSystemLocales();
+ method public void setSystemLocales(@NonNull android.os.LocaleList);
+ }
+
public class Notification implements android.os.Parcelable {
method public boolean shouldShowForegroundImmediately();
}
public final class NotificationChannel implements android.os.Parcelable {
method public int getOriginalImportance();
- method public boolean isBlockable();
method public boolean isImportanceLockedByCriticalDeviceFunction();
method public boolean isImportanceLockedByOEM();
method public void lockFields(int);
@@ -303,21 +314,27 @@
public class NotificationManager {
method public void allowAssistantAdjustment(String);
+ method public void cleanUpCallersAfter(long);
method public void disallowAssistantAdjustment(String);
method public android.content.ComponentName getEffectsSuppressor();
method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String);
- method public boolean matchesCallFilter(android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING) public void setToastRateLimitingEnabled(boolean);
method public void updateNotificationChannel(@NonNull String, int, @NonNull android.app.NotificationChannel);
}
public final class PendingIntent implements android.os.Parcelable {
+ method public boolean addCancelListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.PendingIntent.CancelListener);
method @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public boolean intentFilterEquals(@Nullable android.app.PendingIntent);
method @NonNull @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int);
+ method public void removeCancelListener(@NonNull android.app.PendingIntent.CancelListener);
field @Deprecated public static final int FLAG_MUTABLE_UNAUDITED = 33554432; // 0x2000000
}
+ public static interface PendingIntent.CancelListener {
+ method public void onCanceled(@NonNull android.app.PendingIntent);
+ }
+
public final class PictureInPictureParams implements android.os.Parcelable {
method public java.util.List<android.app.RemoteAction> getActions();
method public float getAspectRatio();
@@ -330,6 +347,7 @@
}
public class StatusBarManager {
+ method public void cancelRequestAddTile(@NonNull String);
method public void clickNotification(@Nullable String, int, int, boolean);
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void collapsePanels();
method public void expandNotificationsPanel();
@@ -339,6 +357,10 @@
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void togglePanel();
}
+ public static final class StatusBarManager.DisableInfo {
+ method public boolean isRotationSuggestionDisabled();
+ }
+
public final class SyncNotedAppOp implements android.os.Parcelable {
ctor public SyncNotedAppOp(int, @IntRange(from=0L) int, @Nullable String, @NonNull String);
}
@@ -424,11 +446,10 @@
package android.app.admin {
public class DevicePolicyManager {
- method public int checkProvisioningPreCondition(@Nullable String, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void clearOrganizationId();
method @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord();
- method @Nullable public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs();
- method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void forceRemoveActiveAdmin(@NonNull android.content.ComponentName, int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void forceRemoveActiveAdmin(@NonNull android.content.ComponentName, int);
method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs();
method public void forceUpdateUserSetupComplete(int);
method @NonNull public java.util.Set<java.lang.String> getDefaultCrossProfilePackages();
@@ -437,35 +458,20 @@
method public long getLastNetworkLogRetrievalTime();
method public long getLastSecurityLogRetrievalTime();
method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(@NonNull android.os.UserHandle);
- method @NonNull @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public java.util.Set<java.lang.String> getPolicyExemptApps();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public java.util.Set<java.lang.String> getPolicyExemptApps();
method public boolean isCurrentInputMethodSetByOwner();
method public boolean isFactoryResetProtectionPolicySupported();
- method @RequiresPermission(anyOf={"android.permission.MARK_DEVICE_ORGANIZATION_OWNED", "android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"}, conditional=true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MARK_DEVICE_ORGANIZATION_OWNED, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}, conditional=true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName);
method @NonNull public static String operationSafetyReasonToString(int);
method @NonNull public static String operationToString(int);
- method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
- method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void resetDefaultCrossProfileIntentFilters(int);
- method @RequiresPermission(allOf={"android.permission.MANAGE_DEVICE_ADMINS", android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int);
- method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int);
- method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void resetDefaultCrossProfileIntentFilters(int);
+ method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, @Nullable String, int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public void setNextOperationSafety(int, int);
field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED";
- field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
- field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
- field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
- field public static final int CODE_HAS_DEVICE_OWNER = 1; // 0x1
- field public static final int CODE_HAS_PAIRED = 8; // 0x8
- field public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
- field public static final int CODE_NONSYSTEM_USER_EXISTS = 5; // 0x5
- field public static final int CODE_NOT_SYSTEM_USER = 7; // 0x7
- field @Deprecated public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; // 0xc
- field public static final int CODE_OK = 0; // 0x0
- field public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf
field @Deprecated public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
- field public static final int CODE_SYSTEM_USER = 10; // 0xa
- field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2
- field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3
- field public static final int CODE_USER_SETUP_COMPLETED = 4; // 0x4
field public static final int OPERATION_CLEAR_APPLICATION_USER_DATA = 23; // 0x17
field public static final int OPERATION_CREATE_AND_MANAGE_USER = 5; // 0x5
field public static final int OPERATION_INSTALL_CA_CERT = 24; // 0x18
@@ -507,66 +513,6 @@
field public static final int OPERATION_SWITCH_USER = 2; // 0x2
field public static final int OPERATION_UNINSTALL_CA_CERT = 40; // 0x28
field public static final int OPERATION_WIPE_DATA = 8; // 0x8
- field public static final int PROVISIONING_RESULT_ADMIN_PACKAGE_INSTALLATION_FAILED = 3; // 0x3
- field public static final int PROVISIONING_RESULT_PRE_CONDITION_FAILED = 1; // 0x1
- field public static final int PROVISIONING_RESULT_PROFILE_CREATION_FAILED = 2; // 0x2
- field public static final int PROVISIONING_RESULT_REMOVE_NON_REQUIRED_APPS_FAILED = 6; // 0x6
- field public static final int PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED = 4; // 0x4
- field public static final int PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED = 7; // 0x7
- field public static final int PROVISIONING_RESULT_STARTING_PROFILE_FAILED = 5; // 0x5
- }
-
- public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable {
- method public boolean canDeviceOwnerGrantSensorsPermissions();
- method public int describeContents();
- method @NonNull public android.content.ComponentName getDeviceAdminComponentName();
- method public long getLocalTime();
- method @Nullable public java.util.Locale getLocale();
- method @NonNull public String getOwnerName();
- method @Nullable public String getTimeZone();
- method public boolean isLeaveAllSystemAppsEnabled();
- method public void logParams(@NonNull String);
- method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FullyManagedDeviceProvisioningParams> CREATOR;
- }
-
- public static final class FullyManagedDeviceProvisioningParams.Builder {
- ctor public FullyManagedDeviceProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
- method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams build();
- method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setDeviceOwnerCanGrantSensorsPermissions(boolean);
- method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
- method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocalTime(long);
- method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocale(@Nullable java.util.Locale);
- method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setTimeZone(@Nullable String);
- }
-
- public final class ManagedProfileProvisioningParams implements android.os.Parcelable {
- method public int describeContents();
- method @Nullable public android.accounts.Account getAccountToMigrate();
- method @NonNull public String getOwnerName();
- method @NonNull public android.content.ComponentName getProfileAdminComponentName();
- method @Nullable public String getProfileName();
- method public boolean isKeepAccountMigrated();
- method public boolean isLeaveAllSystemAppsEnabled();
- method public boolean isOrganizationOwnedProvisioning();
- method public void logParams(@NonNull String);
- method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.ManagedProfileProvisioningParams> CREATOR;
- }
-
- public static final class ManagedProfileProvisioningParams.Builder {
- ctor public ManagedProfileProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
- method @NonNull public android.app.admin.ManagedProfileProvisioningParams build();
- method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setAccountToMigrate(@Nullable android.accounts.Account);
- method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setKeepAccountMigrated(boolean);
- method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
- method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setOrganizationOwnedProvisioning(boolean);
- method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setProfileName(@Nullable String);
- }
-
- public class ProvisioningException extends android.util.AndroidException {
- ctor public ProvisioningException(@NonNull Exception, int);
- method public int getProvisioningResult();
}
public static final class SecurityLog.SecurityEvent implements android.os.Parcelable {
@@ -609,6 +555,14 @@
}
+package android.app.communal {
+
+ public final class CommunalManager {
+ method @RequiresPermission(android.Manifest.permission.WRITE_COMMUNAL_STATE) public void setCommunalViewShowing(boolean);
+ }
+
+}
+
package android.app.contentsuggestions {
public final class ContentSuggestionsManager {
@@ -677,6 +631,14 @@
}
+package android.companion {
+
+ public abstract class CompanionDeviceService extends android.app.Service {
+ method public void onBindCompanionDeviceService(@NonNull android.content.Intent);
+ }
+
+}
+
package android.content {
public final class AttributionSource implements android.os.Parcelable {
@@ -741,6 +703,7 @@
field public static final String FONT_SERVICE = "font";
field public static final String POWER_EXEMPTION_SERVICE = "power_exemption";
field @Deprecated public static final String POWER_WHITELIST_MANAGER = "power_whitelist";
+ field @Deprecated public static final int RECEIVER_EXPORTED_UNAUDITED = 2; // 0x2
field public static final String TEST_NETWORK_SERVICE = "test_network";
}
@@ -780,6 +743,7 @@
field public static final float OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE = 1.7777778f;
field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL
field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
+ field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
}
@@ -809,7 +773,8 @@
method @Nullable public String getDefaultTextClassifierPackageName();
method @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) public android.os.IBinder getHoldLockToken();
method public abstract int getInstallReason(@NonNull String, @NonNull android.os.UserHandle);
- method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
+ method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
+ method @NonNull public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(@NonNull android.content.pm.PackageManager.ApplicationInfoFlags, int);
method @Nullable public abstract String[] getNamesForUids(int[]);
method @NonNull public String getPermissionControllerPackageName();
method @NonNull public abstract String getServicesSystemSharedLibraryPackageName();
@@ -819,6 +784,7 @@
method public void holdLock(android.os.IBinder, int);
method @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES) public void setKeepUninstalledPackages(@NonNull java.util.List<java.lang.String>);
field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
+ field public static final String FEATURE_COMMUNAL_MODE = "android.software.communal_mode";
field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
field public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
field public static final int FLAG_PERMISSION_REVOKE_WHEN_REQUESTED = 128; // 0x80
@@ -1051,13 +1017,13 @@
public final class SensorPrivacyManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, int, boolean);
- method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyForProfileGroup(int, int, boolean);
}
public static class SensorPrivacyManager.Sources {
field public static final int DIALOG = 3; // 0x3
field public static final int OTHER = 5; // 0x5
field public static final int QS_TILE = 1; // 0x1
+ field public static final int SAFETY_HUB = 6; // 0x6
field public static final int SETTINGS = 2; // 0x2
field public static final int SHELL = 4; // 0x4
}
@@ -1133,10 +1099,10 @@
package android.hardware.devicestate {
public final class DeviceStateManager {
- method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest);
+ method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest);
method @NonNull public int[] getSupportedStates();
method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
- method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
+ method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
method public void unregisterCallback(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff
field public static final int MINIMUM_DEVICE_STATE = 0; // 0x0
@@ -1179,12 +1145,15 @@
public final class DisplayManager {
method public boolean areUserDisabledHdrTypesAllowed();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearUserPreferredDisplayMode();
method @NonNull public int[] getUserDisabledHdrTypes();
+ method @Nullable public android.view.Display.Mode getUserPreferredDisplayMode();
method public boolean isMinimalPostProcessingRequested(int);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAreUserDisabledHdrTypesAllowed(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE) public void setRefreshRateSwitchingType(int);
method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public void setShouldAlwaysRespectAppRequestedMode(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setUserDisabledHdrTypes(@NonNull int[]);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public boolean shouldAlwaysRespectAppRequestedMode();
field public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2; // 0x2
field public static final int SWITCHING_TYPE_NONE = 0; // 0x0
@@ -1403,6 +1372,7 @@
package android.media {
public final class AudioAttributes implements android.os.Parcelable {
+ method public static int[] getSdkUsages();
method @NonNull public static String usageToXsdString(int);
method public static int xsdStringToUsage(@NonNull String);
}
@@ -1425,12 +1395,20 @@
public class AudioManager {
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int abandonAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioTrack getCallUplinkInjectionAudioTrack(@NonNull android.media.AudioFormat);
method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int);
method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFadeOutDurationOnFocusLossMillis(@NonNull android.media.AudioAttributes);
+ method public static final int[] getPublicStreamTypes();
+ method @NonNull public java.util.List<java.lang.Integer> getReportedSurroundFormats();
+ method public int getStreamMinVolumeInt(int);
method @NonNull public java.util.Map<java.lang.Integer,java.lang.Boolean> getSurroundFormats();
method public boolean hasRegisteredDynamicPolicy();
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE}) public boolean isFullVolumeDevice();
+ method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int);
+ method public void setRampingRingerEnabled(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean);
}
public static final class AudioRecord.MetricsConstants {
@@ -1451,9 +1429,13 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) public static float getMasterBalance();
method public static final int getNumStreamTypes();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) public static int setMasterBalance(float);
+ method @NonNull public static String streamToString(int);
field public static final int DEVICE_ROLE_DISABLED = 2; // 0x2
field public static final int DEVICE_ROLE_NONE = 0; // 0x0
field public static final int DEVICE_ROLE_PREFERRED = 1; // 0x1
+ field public static final int DIRECT_BITSTREAM_SUPPORTED = 4; // 0x4
+ field public static final int DIRECT_OFFLOAD_GAPLESS_SUPPORTED = 3; // 0x3
+ field public static final int DIRECT_OFFLOAD_SUPPORTED = 1; // 0x1
field public static final int OFFLOAD_GAPLESS_SUPPORTED = 2; // 0x2
field public static final int OFFLOAD_SUPPORTED = 1; // 0x1
field public static final int STREAM_DEFAULT = -1; // 0xffffffff
@@ -1548,6 +1530,13 @@
method @NonNull public android.media.audiopolicy.AudioPolicy.Builder setIsTestFocusPolicy(boolean);
}
+ public final class AudioProductStrategy implements android.os.Parcelable {
+ method @NonNull public static android.media.AudioAttributes getDefaultAttributes();
+ method public int getLegacyStreamTypeForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method public int getVolumeGroupIdForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method public int getVolumeGroupIdForLegacyStreamType(int);
+ }
+
}
package android.media.metrics {
@@ -1787,7 +1776,7 @@
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String);
- method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public String getUserType();
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
method public static boolean isGuestUserEphemeral();
@@ -1799,10 +1788,6 @@
method public int getAudioUsage();
}
- public static final class VibrationAttributes.Builder {
- ctor public VibrationAttributes.Builder(@NonNull android.media.AudioAttributes, @Nullable android.os.VibrationEffect);
- }
-
public abstract class VibrationEffect implements android.os.Parcelable {
method public static android.os.VibrationEffect get(int);
method public static android.os.VibrationEffect get(int, boolean);
@@ -1819,13 +1804,9 @@
}
public static final class VibrationEffect.Composed extends android.os.VibrationEffect {
- method @NonNull public android.os.VibrationEffect.Composed applyEffectStrength(int);
method public long getDuration();
method public int getRepeatIndex();
method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments();
- method @NonNull public android.os.VibrationEffect.Composed resolve(int);
- method @NonNull public android.os.VibrationEffect.Composed scale(float);
- method public void validate();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Composed> CREATOR;
}
@@ -1941,6 +1922,7 @@
}
public class StorageManager {
+ method public long computeStorageCacheBytes(@NonNull java.io.File);
method @NonNull public static java.util.UUID convert(@NonNull String);
method @NonNull public static String convert(@NonNull java.util.UUID);
method public boolean isAppIoBlocked(@NonNull java.util.UUID, int, int, int);
@@ -1973,72 +1955,47 @@
package android.os.vibrator {
public final class PrebakedSegment extends android.os.vibrator.VibrationEffectSegment {
- method @NonNull public android.os.vibrator.PrebakedSegment applyEffectStrength(int);
method public int describeContents();
method public long getDuration();
method public int getEffectId();
method public int getEffectStrength();
- method public boolean hasNonZeroAmplitude();
- method @NonNull public android.os.vibrator.PrebakedSegment resolve(int);
- method @NonNull public android.os.vibrator.PrebakedSegment scale(float);
method public boolean shouldFallback();
- method public void validate();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrebakedSegment> CREATOR;
}
public final class PrimitiveSegment extends android.os.vibrator.VibrationEffectSegment {
- method @NonNull public android.os.vibrator.PrimitiveSegment applyEffectStrength(int);
method public int describeContents();
method public int getDelay();
method public long getDuration();
method public int getPrimitiveId();
method public float getScale();
- method public boolean hasNonZeroAmplitude();
- method @NonNull public android.os.vibrator.PrimitiveSegment resolve(int);
- method @NonNull public android.os.vibrator.PrimitiveSegment scale(float);
- method public void validate();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrimitiveSegment> CREATOR;
}
public final class RampSegment extends android.os.vibrator.VibrationEffectSegment {
- method @NonNull public android.os.vibrator.RampSegment applyEffectStrength(int);
method public int describeContents();
method public long getDuration();
method public float getEndAmplitude();
method public float getEndFrequency();
method public float getStartAmplitude();
method public float getStartFrequency();
- method public boolean hasNonZeroAmplitude();
- method @NonNull public android.os.vibrator.RampSegment resolve(int);
- method @NonNull public android.os.vibrator.RampSegment scale(float);
- method public void validate();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.RampSegment> CREATOR;
}
public final class StepSegment extends android.os.vibrator.VibrationEffectSegment {
- method @NonNull public android.os.vibrator.StepSegment applyEffectStrength(int);
method public int describeContents();
method public float getAmplitude();
method public long getDuration();
method public float getFrequency();
- method public boolean hasNonZeroAmplitude();
- method @NonNull public android.os.vibrator.StepSegment resolve(int);
- method @NonNull public android.os.vibrator.StepSegment scale(float);
- method public void validate();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.StepSegment> CREATOR;
}
public abstract class VibrationEffectSegment implements android.os.Parcelable {
- method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T applyEffectStrength(int);
method public abstract long getDuration();
- method public abstract boolean hasNonZeroAmplitude();
- method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T resolve(int);
- method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T scale(float);
- method public abstract void validate();
field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.VibrationEffectSegment> CREATOR;
}
@@ -2112,7 +2069,7 @@
}
public static final class ContactsContract.CommonDataKinds.Phone implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
- field public static final android.net.Uri ENTERPRISE_CONTENT_URI;
+ field @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public static final android.net.Uri ENTERPRISE_CONTENT_URI;
}
public static final class ContactsContract.PinnedPositions {
@@ -2130,6 +2087,7 @@
field public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
field public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
+ field public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
}
public final class Settings {
@@ -2155,7 +2113,10 @@
field public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices";
field public static final String SHOW_FIRST_CRASH_DIALOG = "show_first_crash_dialog";
field public static final String USER_DISABLED_HDR_FORMATS = "user_disabled_hdr_formats";
- field public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
+ field public static final String USER_PREFERRED_REFRESH_RATE = "user_preferred_refresh_rate";
+ field public static final String USER_PREFERRED_RESOLUTION_HEIGHT = "user_preferred_resolution_height";
+ field public static final String USER_PREFERRED_RESOLUTION_WIDTH = "user_preferred_resolution_width";
+ field @Deprecated public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
}
public static final class Settings.Secure extends android.provider.Settings.NameValueTable {
@@ -2201,8 +2162,8 @@
}
public class KeyStoreException extends java.lang.Exception {
- ctor public KeyStoreException(int, String);
method public int getErrorCode();
+ method public static boolean hasFailureInfoForError(int);
}
}
@@ -2337,6 +2298,17 @@
}
+package android.service.dreams {
+
+ public abstract class DreamOverlayService extends android.app.Service {
+ ctor public DreamOverlayService();
+ method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public abstract void onStartDream(@NonNull android.view.WindowManager.LayoutParams);
+ method public final void requestExit();
+ }
+
+}
+
package android.service.notification {
@Deprecated public abstract class ConditionProviderService extends android.app.Service {
@@ -2389,6 +2361,10 @@
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public void triggerHardwareRecognitionEventForTest(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[]);
}
+ public final class VisibleActivityInfo implements android.os.Parcelable {
+ ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder);
+ }
+
}
package android.service.watchdog {
@@ -2722,6 +2698,7 @@
}
public final class Display {
+ method @NonNull public android.view.Display.Mode getDefaultMode();
method @NonNull public int[] getReportedHdrTypes();
method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut();
method public int getType();
@@ -2735,6 +2712,11 @@
field public static final int TYPE_WIFI = 3; // 0x3
}
+ public static final class Display.Mode implements android.os.Parcelable {
+ ctor public Display.Mode(int, int, float);
+ method public boolean matches(int, int, float);
+ }
+
public class FocusFinder {
method public static void sort(android.view.View[], int, int, android.view.ViewGroup, boolean);
}
@@ -2748,7 +2730,7 @@
method public static String actionToString(int);
method public final void setDisplayId(int);
field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
- field public static final int LAST_KEYCODE = 288; // 0x120
+ field public static final int LAST_KEYCODE = 304; // 0x130
}
public final class KeyboardShortcutGroup implements android.os.Parcelable {
@@ -2774,11 +2756,9 @@
public final class SurfaceControl implements android.os.Parcelable {
ctor public SurfaceControl(@NonNull android.view.SurfaceControl, @NonNull String);
- method public static long acquireFrameRateFlexibilityToken();
method @NonNull public static android.os.IBinder getInternalDisplayToken();
method public boolean isSameSurface(@NonNull android.view.SurfaceControl);
method public static void overrideHdrTypes(@NonNull android.os.IBinder, @NonNull int[]);
- method public static void releaseFrameRateFlexibilityToken(long);
}
public class SurfaceControlViewHost {
@@ -2840,6 +2820,7 @@
method @Nullable public final android.os.IBinder getWindowContextToken();
method public final void setWindowContextToken(@NonNull android.os.IBinder);
field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
+ field public static final int FLAG_SLIPPERY = 536870912; // 0x20000000
field public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 64; // 0x40
field public CharSequence accessibilityTitle;
field public int privateFlags;
@@ -2850,20 +2831,13 @@
package android.view.accessibility {
public final class AccessibilityManager {
- method public void addAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener, @Nullable android.os.Handler);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public java.util.List<java.lang.String> getAccessibilityShortcutTargets(int);
- method public void removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
- }
-
- public static interface AccessibilityManager.AccessibilityServicesStateChangeListener {
- method public void onAccessibilityServicesStateChanged(android.view.accessibility.AccessibilityManager);
}
public class AccessibilityNodeInfo implements android.os.Parcelable {
method public void addChild(@NonNull android.os.IBinder);
method public long getSourceNodeId();
method public void setLeashedParent(@Nullable android.os.IBinder, int);
- method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
method public void writeToParcelNoRecycle(android.os.Parcel, int);
}
@@ -2871,6 +2845,10 @@
method public long getAccessibilityIdForRegion(@NonNull android.graphics.Region);
}
+ public class AccessibilityRecord {
+ method public void setDisplayId(int);
+ }
+
public final class AccessibilityWindowInfo implements android.os.Parcelable {
method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
}
@@ -2899,6 +2877,7 @@
}
public final class AutofillManager {
+ field public static final String DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES = "compat_mode_allowed_packages";
field public static final String DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES = "smart_suggestion_supported_modes";
field public static final int FLAG_SMART_SUGGESTION_OFF = 0; // 0x0
field public static final int FLAG_SMART_SUGGESTION_SYSTEM = 1; // 0x1
@@ -3182,14 +3161,6 @@
public final class SplashScreenView extends android.widget.FrameLayout {
method @Nullable public android.view.View getBrandingView();
- method @ColorInt public int getIconBackgroundColor();
- }
-
- public final class StartingWindowInfo implements android.os.Parcelable {
- ctor public StartingWindowInfo();
- method public int describeContents();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.window.StartingWindowInfo> CREATOR;
}
public final class TaskAppearedInfo implements android.os.Parcelable {
@@ -3201,9 +3172,57 @@
field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskAppearedInfo> CREATOR;
}
+ public final class TaskFragmentCreationParams implements android.os.Parcelable {
+ method @NonNull public android.os.IBinder getFragmentToken();
+ method @NonNull public android.graphics.Rect getInitialBounds();
+ method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizer();
+ method @NonNull public android.os.IBinder getOwnerToken();
+ method public int getWindowingMode();
+ field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentCreationParams> CREATOR;
+ }
+
+ public static final class TaskFragmentCreationParams.Builder {
+ ctor public TaskFragmentCreationParams.Builder(@NonNull android.window.TaskFragmentOrganizerToken, @NonNull android.os.IBinder, @NonNull android.os.IBinder);
+ method @NonNull public android.window.TaskFragmentCreationParams build();
+ method @NonNull public android.window.TaskFragmentCreationParams.Builder setInitialBounds(@NonNull android.graphics.Rect);
+ method @NonNull public android.window.TaskFragmentCreationParams.Builder setWindowingMode(int);
+ }
+
+ public final class TaskFragmentInfo implements android.os.Parcelable {
+ method public boolean equalsForTaskFragmentOrganizer(@Nullable android.window.TaskFragmentInfo);
+ method @NonNull public java.util.List<android.os.IBinder> getActivities();
+ method @NonNull public android.content.res.Configuration getConfiguration();
+ method @NonNull public android.os.IBinder getFragmentToken();
+ method @NonNull public android.graphics.Point getPositionInParent();
+ method public int getRunningActivityCount();
+ method @NonNull public android.window.WindowContainerToken getToken();
+ method public int getWindowingMode();
+ method public boolean hasRunningActivity();
+ method public boolean isEmpty();
+ method public boolean isTaskClearedForReuse();
+ method public boolean isVisible();
+ field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentInfo> CREATOR;
+ }
+
+ public class TaskFragmentOrganizer extends android.window.WindowOrganizer {
+ ctor public TaskFragmentOrganizer(@NonNull java.util.concurrent.Executor);
+ method @NonNull public java.util.concurrent.Executor getExecutor();
+ method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizerToken();
+ method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentInfo);
+ method public void onTaskFragmentError(@NonNull android.os.IBinder, @NonNull Throwable);
+ method public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo);
+ method public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration);
+ method public void onTaskFragmentVanished(@NonNull android.window.TaskFragmentInfo);
+ method @CallSuper public void registerOrganizer();
+ method @CallSuper public void unregisterOrganizer();
+ }
+
+ public final class TaskFragmentOrganizerToken implements android.os.Parcelable {
+ field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR;
+ }
+
public class TaskOrganizer extends android.window.WindowOrganizer {
ctor public TaskOrganizer();
- method @BinderThread public void addStartingWindow(@NonNull android.window.StartingWindowInfo, @NonNull android.os.IBinder);
method @BinderThread public void copySplashScreenView(int);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void createRootTask(int, int, @Nullable android.os.IBinder);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean deleteRootTask(@NonNull android.window.WindowContainerToken);
@@ -3216,7 +3235,6 @@
method @BinderThread public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo);
method @BinderThread public void onTaskVanished(@NonNull android.app.ActivityManager.RunningTaskInfo);
method @CallSuper @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.window.TaskAppearedInfo> registerOrganizer();
- method @BinderThread public void removeStartingWindow(int, @Nullable android.view.SurfaceControl, @Nullable android.graphics.Rect, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void setInterceptBackPressedOnTaskRoot(@NonNull android.window.WindowContainerToken, boolean);
method @CallSuper @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void unregisterOrganizer();
}
@@ -3229,26 +3247,42 @@
public final class WindowContainerTransaction implements android.os.Parcelable {
ctor public WindowContainerTransaction();
+ method @NonNull public android.window.WindowContainerTransaction createTaskFragment(@NonNull android.window.TaskFragmentCreationParams);
+ method @NonNull public android.window.WindowContainerTransaction deleteTaskFragment(@NonNull android.window.WindowContainerToken);
method public int describeContents();
method @NonNull public android.window.WindowContainerTransaction reorder(@NonNull android.window.WindowContainerToken, boolean);
method @NonNull public android.window.WindowContainerTransaction reparent(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, boolean);
+ method @NonNull public android.window.WindowContainerTransaction reparentActivityToTaskFragment(@NonNull android.os.IBinder, @NonNull android.os.IBinder);
+ method @NonNull public android.window.WindowContainerTransaction reparentChildren(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken);
method @NonNull public android.window.WindowContainerTransaction reparentTasks(@Nullable android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, @Nullable int[], @Nullable int[], boolean);
method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int);
- method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken);
+ method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken, boolean);
+ method @NonNull public android.window.WindowContainerTransaction setAdjacentTaskFragments(@NonNull android.os.IBinder, @Nullable android.os.IBinder, @Nullable android.window.WindowContainerTransaction.TaskFragmentAdjacentParams);
method @NonNull public android.window.WindowContainerTransaction setAppBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
method @NonNull public android.window.WindowContainerTransaction setBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
method @NonNull public android.window.WindowContainerTransaction setBoundsChangeTransaction(@NonNull android.window.WindowContainerToken, @NonNull android.view.SurfaceControl.Transaction);
+ method @NonNull public android.window.WindowContainerTransaction setErrorCallbackToken(@NonNull android.os.IBinder);
method @NonNull public android.window.WindowContainerTransaction setFocusable(@NonNull android.window.WindowContainerToken, boolean);
method @NonNull public android.window.WindowContainerTransaction setHidden(@NonNull android.window.WindowContainerToken, boolean);
method @NonNull public android.window.WindowContainerTransaction setLaunchRoot(@NonNull android.window.WindowContainerToken, @Nullable int[], @Nullable int[]);
method @NonNull public android.window.WindowContainerTransaction setScreenSizeDp(@NonNull android.window.WindowContainerToken, int, int);
method @NonNull public android.window.WindowContainerTransaction setSmallestScreenWidthDp(@NonNull android.window.WindowContainerToken, int);
method @NonNull public android.window.WindowContainerTransaction setWindowingMode(@NonNull android.window.WindowContainerToken, int);
+ method @NonNull public android.window.WindowContainerTransaction startActivityInTaskFragment(@NonNull android.os.IBinder, @NonNull android.os.IBinder, @NonNull android.content.Intent, @Nullable android.os.Bundle);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.window.WindowContainerTransaction> CREATOR;
}
+ public static class WindowContainerTransaction.TaskFragmentAdjacentParams {
+ ctor public WindowContainerTransaction.TaskFragmentAdjacentParams();
+ ctor public WindowContainerTransaction.TaskFragmentAdjacentParams(@NonNull android.os.Bundle);
+ method public void setShouldDelayPrimaryLastActivityRemoval(boolean);
+ method public void setShouldDelaySecondaryLastActivityRemoval(boolean);
+ method public boolean shouldDelayPrimaryLastActivityRemoval();
+ method public boolean shouldDelaySecondaryLastActivityRemoval();
+ }
+
public abstract class WindowContainerTransactionCallback {
ctor public WindowContainerTransactionCallback();
method public abstract void onTransactionReady(int, @NonNull android.view.SurfaceControl.Transaction);
@@ -3256,15 +3290,15 @@
public class WindowOrganizer {
ctor public WindowOrganizer();
- method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback);
- method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void applyTransaction(@NonNull android.window.WindowContainerTransaction);
+ method @RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback);
+ method @RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true) public void applyTransaction(@NonNull android.window.WindowContainerTransaction);
}
@UiContext public abstract class WindowProviderService extends android.app.Service {
ctor public WindowProviderService();
method public final void attachToWindowToken(@NonNull android.os.IBinder);
method @NonNull public int getInitialDisplayId();
- method @Nullable public android.os.Bundle getWindowContextOptions();
+ method @CallSuper @Nullable public android.os.Bundle getWindowContextOptions();
method public abstract int getWindowType();
}
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/wifi-current.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/wifi-current.txt
index c5d9c2f..3dd2893 100644
--- a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/wifi-current.txt
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/wifi-current.txt
@@ -25,6 +25,8 @@
method public static int convertFrequencyMhzToChannelIfSupported(int);
method public int describeContents();
method @NonNull public java.util.List<android.net.wifi.ScanResult.InformationElement> getInformationElements();
+ method @NonNull public int[] getSecurityTypes();
+ method @Nullable public android.net.wifi.WifiSsid getWifiSsid();
method public int getWifiStandard();
method public boolean is80211mcResponder();
method public boolean isPasspointNetwork();
@@ -36,7 +38,11 @@
field public static final int CHANNEL_WIDTH_80MHZ = 2; // 0x2
field public static final int CHANNEL_WIDTH_80MHZ_PLUS_MHZ = 4; // 0x4
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.ScanResult> CREATOR;
- field public String SSID;
+ field public static final int PREAMBLE_HE = 3; // 0x3
+ field public static final int PREAMBLE_HT = 1; // 0x1
+ field public static final int PREAMBLE_LEGACY = 0; // 0x0
+ field public static final int PREAMBLE_VHT = 2; // 0x2
+ field @Deprecated public String SSID;
field public static final int UNSPECIFIED = -1; // 0xffffffff
field public static final int WIFI_BAND_24_GHZ = 1; // 0x1
field public static final int WIFI_BAND_5_GHZ = 2; // 0x2
@@ -74,7 +80,8 @@
method @Nullable public android.net.MacAddress getBssid();
method @Nullable public String getPassphrase();
method public int getSecurityType();
- method @Nullable public String getSsid();
+ method @Deprecated @Nullable public String getSsid();
+ method @Nullable public android.net.wifi.WifiSsid getWifiSsid();
method public boolean isHiddenSsid();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApConfiguration> CREATOR;
@@ -109,13 +116,19 @@
method public int describeContents();
method @Deprecated public android.net.ProxyInfo getHttpProxy();
method @Deprecated @NonNull public String getKey();
+ method @Deprecated public int getMacRandomizationSetting();
method @Deprecated @NonNull public android.net.MacAddress getRandomizedMacAddress();
method @Deprecated public boolean isPasspoint();
method @Deprecated public void setHttpProxy(android.net.ProxyInfo);
+ method @Deprecated public void setMacRandomizationSetting(int);
method @Deprecated public void setSecurityParams(int);
method public void writeToParcel(android.os.Parcel, int);
field @Deprecated public String BSSID;
field @Deprecated public String FQDN;
+ field @Deprecated public static final int RANDOMIZATION_AUTO = 3; // 0x3
+ field @Deprecated public static final int RANDOMIZATION_NONE = 0; // 0x0
+ field @Deprecated public static final int RANDOMIZATION_NON_PERSISTENT = 2; // 0x2
+ field @Deprecated public static final int RANDOMIZATION_PERSISTENT = 1; // 0x1
field @Deprecated public static final int SECURITY_TYPE_EAP = 3; // 0x3
field @Deprecated public static final int SECURITY_TYPE_EAP_SUITE_B = 5; // 0x5
field @Deprecated public static final int SECURITY_TYPE_EAP_WPA3_ENTERPRISE = 9; // 0x9
@@ -297,7 +310,7 @@
method @Nullable public java.util.List<android.net.wifi.ScanResult.InformationElement> getInformationElements();
method @Deprecated public int getIpAddress();
method public int getLinkSpeed();
- method @RequiresPermission(allOf={android.Manifest.permission.LOCAL_MAC_ADDRESS, android.Manifest.permission.ACCESS_FINE_LOCATION}) public String getMacAddress();
+ method public String getMacAddress();
method public int getMaxSupportedRxLinkSpeedMbps();
method public int getMaxSupportedTxLinkSpeedMbps();
method public int getNetworkId();
@@ -310,6 +323,7 @@
method public android.net.wifi.SupplicantState getSupplicantState();
method @IntRange(from=0xffffffff) public int getTxLinkSpeedMbps();
method public int getWifiStandard();
+ method public boolean isRestricted();
method @NonNull public android.net.wifi.WifiInfo makeCopy(long);
method public void writeToParcel(android.os.Parcel, int);
field public static final String FREQUENCY_UNITS = "MHz";
@@ -343,7 +357,7 @@
public class WifiManager {
method @Deprecated public int addNetwork(android.net.wifi.WifiConfiguration);
- method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_MANAGED_PROVISIONING}) public android.net.wifi.WifiManager.AddNetworkResult addNetworkPrivileged(@NonNull android.net.wifi.WifiConfiguration);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_MANAGED_PROVISIONING}, conditional=true) public android.net.wifi.WifiManager.AddNetworkResult addNetworkPrivileged(@NonNull android.net.wifi.WifiConfiguration);
method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public int addNetworkSuggestions(@NonNull java.util.List<android.net.wifi.WifiNetworkSuggestion>);
method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public void addSuggestionConnectionStatusListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SuggestionConnectionStatusListener);
@@ -358,16 +372,16 @@
method @Deprecated public boolean disableNetwork(int);
method @Deprecated public boolean disconnect();
method @Deprecated public boolean enableNetwork(int, boolean);
- method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_MANAGED_PROVISIONING, android.Manifest.permission.NETWORK_CARRIER_PROVISIONING}) public void flushPasspointAnqpCache();
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_MANAGED_PROVISIONING, android.Manifest.permission.NETWORK_CARRIER_PROVISIONING}, conditional=true) public void flushPasspointAnqpCache();
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public java.util.List<android.net.wifi.WifiConfiguration> getCallerConfiguredNetworks();
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
- method @Deprecated public android.net.wifi.WifiInfo getConnectionInfo();
+ method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public android.net.wifi.WifiInfo getConnectionInfo();
method @Deprecated public android.net.DhcpInfo getDhcpInfo();
method public int getMaxNumberOfNetworkSuggestionsPerApp();
method @IntRange(from=0) public int getMaxSignalLevel();
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public java.util.List<android.net.wifi.WifiNetworkSuggestion> getNetworkSuggestions();
- method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.hotspot2.PasspointConfiguration> getPasspointConfigurations();
- method public java.util.List<android.net.wifi.ScanResult> getScanResults();
+ method @Deprecated public java.util.List<android.net.wifi.hotspot2.PasspointConfiguration> getPasspointConfigurations();
+ method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public java.util.List<android.net.wifi.ScanResult> getScanResults();
method public int getWifiState();
method public boolean is24GHzBandSupported();
method public boolean is5GHzBandSupported();
@@ -395,6 +409,7 @@
method public boolean isWapiSupported();
method public boolean isWifiDisplayR2Supported();
method public boolean isWifiEnabled();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiPasspointEnabled();
method public boolean isWifiStandardSupported(int);
method public boolean isWpa3SaeH2eSupported();
method public boolean isWpa3SaePublicKeySupported();
@@ -406,22 +421,25 @@
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void registerScanResultsCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.ScanResultsCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void registerSubsystemRestartTrackingCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SubsystemRestartTrackingCallback);
method @Deprecated public boolean removeNetwork(int);
- method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public int removeNetworkSuggestions(@NonNull java.util.List<android.net.wifi.WifiNetworkSuggestion>);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public int removeNetworkSuggestions(@NonNull java.util.List<android.net.wifi.WifiNetworkSuggestion>);
+ method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public int removeNetworkSuggestions(@NonNull java.util.List<android.net.wifi.WifiNetworkSuggestion>, int);
method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean removeNonCallerConfiguredNetworks();
- method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_CARRIER_PROVISIONING}) public void removePasspointConfiguration(String);
+ method @Deprecated public void removePasspointConfiguration(String);
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void removeSuggestionConnectionStatusListener(@NonNull android.net.wifi.WifiManager.SuggestionConnectionStatusListener);
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void removeSuggestionUserApprovalStatusListener(@NonNull android.net.wifi.WifiManager.SuggestionUserApprovalStatusListener);
method @Deprecated public boolean saveConfiguration();
method public void setTdlsEnabled(java.net.InetAddress, boolean);
method public void setTdlsEnabledWithMacAddress(String, boolean);
method @Deprecated public boolean setWifiEnabled(boolean);
- method @RequiresPermission(allOf={android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void startLocalOnlyHotspot(android.net.wifi.WifiManager.LocalOnlyHotspotCallback, @Nullable android.os.Handler);
+ method @RequiresPermission(allOf={android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.NEARBY_WIFI_DEVICES}, conditional=true) public void startLocalOnlyHotspot(android.net.wifi.WifiManager.LocalOnlyHotspotCallback, @Nullable android.os.Handler);
method @Deprecated public boolean startScan();
method @Deprecated public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void unregisterScanResultsCallback(@NonNull android.net.wifi.WifiManager.ScanResultsCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void unregisterSubsystemRestartTrackingCallback(@NonNull android.net.wifi.WifiManager.SubsystemRestartTrackingCallback);
method @Deprecated public int updateNetwork(android.net.wifi.WifiConfiguration);
field public static final String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
+ field public static final int ACTION_REMOVE_SUGGESTION_DISCONNECT = 2; // 0x2
+ field public static final int ACTION_REMOVE_SUGGESTION_LINGER = 1; // 0x1
field public static final String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
field public static final String ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION = "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION";
field public static final String ACTION_WIFI_SCAN_AVAILABILITY_CHANGED = "android.net.wifi.action.WIFI_SCAN_AVAILABILITY_CHANGED";
@@ -586,11 +604,13 @@
method public int describeContents();
method @Nullable public android.net.MacAddress getBssid();
method @Nullable public android.net.wifi.WifiEnterpriseConfig getEnterpriseConfig();
+ method public int getMacRandomizationSetting();
method @Nullable public String getPassphrase();
method @Nullable public android.net.wifi.hotspot2.PasspointConfiguration getPasspointConfig();
method @IntRange(from=0) public int getPriority();
method @IntRange(from=0) public int getPriorityGroup();
method @Nullable public String getSsid();
+ method @Nullable public android.os.ParcelUuid getSubscriptionGroup();
method public int getSubscriptionId();
method public boolean isAppInteractionRequired();
method public boolean isCarrierMerged();
@@ -599,6 +619,7 @@
method public boolean isHiddenSsid();
method public boolean isInitialAutojoinEnabled();
method public boolean isMetered();
+ method public boolean isRestricted();
method public boolean isUntrusted();
method public boolean isUserInteractionRequired();
method public void writeToParcel(android.os.Parcel, int);
@@ -624,7 +645,9 @@
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPasspointConfig(@NonNull android.net.wifi.hotspot2.PasspointConfiguration);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriority(@IntRange(from=0) int);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriorityGroup(@IntRange(from=0) int);
+ method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setRestricted(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSsid(@NonNull String);
+ method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSubscriptionGroup(@NonNull android.os.ParcelUuid);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSubscriptionId(int);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setUntrusted(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWapiEnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
@@ -637,6 +660,17 @@
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWpa3Passphrase(@NonNull String);
}
+ public final class WifiSsid implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public static android.net.wifi.WifiSsid fromBytes(@Nullable byte[]);
+ method @NonNull public static android.net.wifi.WifiSsid fromString(@Nullable String);
+ method @NonNull public static android.net.wifi.WifiSsid fromUtf8Text(@Nullable CharSequence);
+ method @NonNull public byte[] getBytes();
+ method @Nullable public CharSequence getUtf8Text();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiSsid> CREATOR;
+ }
+
public class WpsInfo implements android.os.Parcelable {
ctor public WpsInfo();
ctor public WpsInfo(android.net.wifi.WpsInfo);
@@ -661,6 +695,7 @@
ctor public AttachCallback();
method public void onAttachFailed();
method public void onAttached(android.net.wifi.aware.WifiAwareSession);
+ method public void onShutDown();
}
public final class AwareResources implements android.os.Parcelable {
@@ -678,6 +713,10 @@
method public int getMaxMatchFilterLength();
method public int getMaxServiceNameLength();
method public int getMaxServiceSpecificInfoLength();
+ method @IntRange(from=1) public int getNumberOfSupportedDataInterfaces();
+ method @IntRange(from=1) public int getNumberOfSupportedDataPaths();
+ method @IntRange(from=1) public int getNumberOfSupportedPublishSessions();
+ method @IntRange(from=1) public int getNumberOfSupportedSubscribeSessions();
method public int getSupportedCipherSuites();
method public boolean isInstantCommunicationModeSupported();
method public void writeToParcel(android.os.Parcel, int);
@@ -773,8 +812,8 @@
}
public class WifiAwareManager {
- method public void attach(@NonNull android.net.wifi.aware.AttachCallback, @Nullable android.os.Handler);
- method public void attach(@NonNull android.net.wifi.aware.AttachCallback, @NonNull android.net.wifi.aware.IdentityChangedListener, @Nullable android.os.Handler);
+ method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_STATE}) public void attach(@NonNull android.net.wifi.aware.AttachCallback, @Nullable android.os.Handler);
+ method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.NEARBY_WIFI_DEVICES}, conditional=true) public void attach(@NonNull android.net.wifi.aware.AttachCallback, @NonNull android.net.wifi.aware.IdentityChangedListener, @Nullable android.os.Handler);
method @Nullable public android.net.wifi.aware.AwareResources getAvailableAwareResources();
method @Nullable public android.net.wifi.aware.Characteristics getCharacteristics();
method public boolean isAvailable();
@@ -816,8 +855,8 @@
method public void close();
method @Deprecated public android.net.NetworkSpecifier createNetworkSpecifierOpen(int, @NonNull byte[]);
method @Deprecated public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(int, @NonNull byte[], @NonNull String);
- method public void publish(@NonNull android.net.wifi.aware.PublishConfig, @NonNull android.net.wifi.aware.DiscoverySessionCallback, @Nullable android.os.Handler);
- method public void subscribe(@NonNull android.net.wifi.aware.SubscribeConfig, @NonNull android.net.wifi.aware.DiscoverySessionCallback, @Nullable android.os.Handler);
+ method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.NEARBY_WIFI_DEVICES}, conditional=true) public void publish(@NonNull android.net.wifi.aware.PublishConfig, @NonNull android.net.wifi.aware.DiscoverySessionCallback, @Nullable android.os.Handler);
+ method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.NEARBY_WIFI_DEVICES}, conditional=true) public void subscribe(@NonNull android.net.wifi.aware.SubscribeConfig, @NonNull android.net.wifi.aware.DiscoverySessionCallback, @Nullable android.os.Handler);
}
}
@@ -1040,30 +1079,32 @@
}
public class WifiP2pManager {
- method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void addLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+ method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void addLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public void addServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public void cancelConnect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public void clearLocalServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public void clearServiceRequests(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void connect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pConfig, android.net.wifi.p2p.WifiP2pManager.ActionListener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pConfig, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+ method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void connect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pConfig, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+ method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void createGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+ method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void createGroup(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pConfig, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
+ method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void discoverPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+ method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void discoverServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public android.net.wifi.p2p.WifiP2pManager.Channel initialize(android.content.Context, android.os.Looper, android.net.wifi.p2p.WifiP2pManager.ChannelListener);
method public void removeGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public void removeLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public void removeServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public void requestConnectionInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestDeviceInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.DeviceInfoListener);
+ method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void requestDeviceInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.DeviceInfoListener);
method public void requestDiscoveryState(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.DiscoveryStateListener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.GroupInfoListener);
+ method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void requestGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.GroupInfoListener);
method public void requestNetworkInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.NetworkInfoListener);
method public void requestP2pState(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.P2pStateListener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PeerListListener);
+ method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void requestPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PeerListListener);
method public void setDnsSdResponseListeners(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.DnsSdServiceResponseListener, android.net.wifi.p2p.WifiP2pManager.DnsSdTxtRecordListener);
method public void setServiceResponseListener(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ServiceResponseListener);
method public void setUpnpServiceResponseListener(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.UpnpServiceResponseListener);
+ method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void startListening(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
+ method public void stopListening(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public void stopPeerDiscovery(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
field public static final int BUSY = 2; // 0x2
field public static final int ERROR = 0; // 0x0
@@ -1282,10 +1323,12 @@
public static final class RangingRequest.Builder {
ctor public RangingRequest.Builder();
- method public android.net.wifi.rtt.RangingRequest.Builder addAccessPoint(@NonNull android.net.wifi.ScanResult);
- method public android.net.wifi.rtt.RangingRequest.Builder addAccessPoints(@NonNull java.util.List<android.net.wifi.ScanResult>);
+ method @NonNull public android.net.wifi.rtt.RangingRequest.Builder addAccessPoint(@NonNull android.net.wifi.ScanResult);
+ method @NonNull public android.net.wifi.rtt.RangingRequest.Builder addAccessPoints(@NonNull java.util.List<android.net.wifi.ScanResult>);
method @NonNull public android.net.wifi.rtt.RangingRequest.Builder addNon80211mcCapableAccessPoint(@NonNull android.net.wifi.ScanResult);
method @NonNull public android.net.wifi.rtt.RangingRequest.Builder addNon80211mcCapableAccessPoints(@NonNull java.util.List<android.net.wifi.ScanResult>);
+ method @NonNull public android.net.wifi.rtt.RangingRequest.Builder addResponder(@NonNull android.net.wifi.rtt.ResponderConfig);
+ method @NonNull public android.net.wifi.rtt.RangingRequest.Builder addResponders(@NonNull java.util.List<android.net.wifi.rtt.ResponderConfig>);
method public android.net.wifi.rtt.RangingRequest.Builder addWifiAwarePeer(@NonNull android.net.MacAddress);
method public android.net.wifi.rtt.RangingRequest.Builder addWifiAwarePeer(@NonNull android.net.wifi.aware.PeerHandle);
method public android.net.wifi.rtt.RangingRequest build();
@@ -1320,6 +1363,32 @@
field public static final int STATUS_CODE_FAIL_RTT_NOT_AVAILABLE = 2; // 0x2
}
+ public final class ResponderConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public static android.net.wifi.rtt.ResponderConfig fromScanResult(@NonNull android.net.wifi.ScanResult);
+ method public int getCenterFreq0Mhz();
+ method public int getCenterFreq1Mhz();
+ method public int getChannelWidth();
+ method public int getFrequencyMhz();
+ method @Nullable public android.net.MacAddress getMacAddress();
+ method public int getPreamble();
+ method public boolean is80211mcSupported();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.rtt.ResponderConfig> CREATOR;
+ }
+
+ public static final class ResponderConfig.Builder {
+ ctor public ResponderConfig.Builder();
+ method @NonNull public android.net.wifi.rtt.ResponderConfig build();
+ method @NonNull public android.net.wifi.rtt.ResponderConfig.Builder set80211mcSupported(boolean);
+ method @NonNull public android.net.wifi.rtt.ResponderConfig.Builder setCenterFreq0Mhz(int);
+ method @NonNull public android.net.wifi.rtt.ResponderConfig.Builder setCenterFreq1Mhz(int);
+ method @NonNull public android.net.wifi.rtt.ResponderConfig.Builder setChannelWidth(int);
+ method @NonNull public android.net.wifi.rtt.ResponderConfig.Builder setFrequencyMhz(int);
+ method @NonNull public android.net.wifi.rtt.ResponderConfig.Builder setMacAddress(@NonNull android.net.MacAddress);
+ method @NonNull public android.net.wifi.rtt.ResponderConfig.Builder setPreamble(int);
+ }
+
public final class ResponderLocation implements android.os.Parcelable {
method public int describeContents();
method public double getAltitude();
@@ -1362,7 +1431,7 @@
public class WifiRttManager {
method public boolean isAvailable();
- method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_WIFI_STATE}) public void startRanging(@NonNull android.net.wifi.rtt.RangingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.rtt.RangingResultCallback);
+ method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.NEARBY_WIFI_DEVICES}) public void startRanging(@NonNull android.net.wifi.rtt.RangingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.rtt.RangingResultCallback);
field public static final String ACTION_WIFI_RTT_STATE_CHANGED = "android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED";
}
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableAccountManagerCallback.java.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableAccountManagerCallback.java.txt
new file mode 100644
index 0000000..4984775
--- /dev/null
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableAccountManagerCallback.java.txt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.remoteframeworkclasses;
+
+import android.accounts.AccountManagerCallback;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.android.enterprise.connectedapps.annotations.CustomParcelableWrapper;
+import com.google.android.enterprise.connectedapps.internal.Bundler;
+import com.google.android.enterprise.connectedapps.internal.BundlerType;
+
+/**
+ * This parcelable wrapper just passes null to callers.
+ *
+ * <p>It is not functional and only enables use of {@link AccountManagerCallback} for clients
+ * which do not need to actually use the {@link AccountManagerCallback} param or return value.
+ */
+@CustomParcelableWrapper(originalType = AccountManagerCallback.class)
+public final class NullParcelableAccountManagerCallback<F> implements Parcelable {
+
+ /**
+ * Create a wrapper for a given {@link AccountManagerCallback}.
+ */
+ public static <F> NullParcelableAccountManagerCallback of(
+ Bundler bundler, BundlerType type,
+ AccountManagerCallback<F> accountManagerCallback) {
+
+ if (accountManagerCallback != null) {
+ throw new IllegalArgumentException("accountManagerCallback can only be null");
+ }
+
+ return new NullParcelableAccountManagerCallback<F>();
+ }
+
+ private NullParcelableAccountManagerCallback() {
+ }
+
+ public AccountManagerCallback<F> get() {
+ return null;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static final Creator<NullParcelableAccountManagerCallback> CREATOR =
+ new Creator<NullParcelableAccountManagerCallback>() {
+ @Override
+ public NullParcelableAccountManagerCallback createFromParcel(Parcel in) {
+ return new NullParcelableAccountManagerCallback();
+ }
+
+ @Override
+ public NullParcelableAccountManagerCallback[] newArray(int size) {
+ return new NullParcelableAccountManagerCallback[size];
+ }
+ };
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableActivity.java.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableActivity.java.txt
new file mode 100644
index 0000000..6000472
--- /dev/null
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableActivity.java.txt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.remoteframeworkclasses;
+
+import android.app.Activity;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.android.enterprise.connectedapps.annotations.CustomParcelableWrapper;
+import com.google.android.enterprise.connectedapps.internal.Bundler;
+import com.google.android.enterprise.connectedapps.internal.BundlerType;
+
+/**
+ * This parcelable wrapper just passes null to callers.
+ *
+ * <p>It is not functional and only enables use of {@link Activity} for clients
+ * which do not need to actually use the {@link Activity} param or return value.
+ */
+@CustomParcelableWrapper(originalType = Activity.class)
+public final class NullParcelableActivity implements Parcelable {
+
+ /**
+ * Create a wrapper for a given {@link Activity}.
+ */
+ public static <F> NullParcelableActivity of(
+ Bundler bundler, BundlerType type,
+ Activity activity) {
+
+ if (activity != null) {
+ throw new IllegalArgumentException("activity can only be null");
+ }
+
+ return new NullParcelableActivity();
+ }
+
+ private NullParcelableActivity() {
+ }
+
+ public Activity get() {
+ return null;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static final Creator<NullParcelableActivity> CREATOR =
+ new Creator<NullParcelableActivity>() {
+ @Override
+ public NullParcelableActivity createFromParcel(Parcel in) {
+ return new NullParcelableActivity();
+ }
+
+ @Override
+ public NullParcelableActivity[] newArray(int size) {
+ return new NullParcelableActivity[size];
+ }
+ };
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableHandler.java.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableHandler.java.txt
new file mode 100644
index 0000000..92692ad
--- /dev/null
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableHandler.java.txt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.remoteframeworkclasses;
+
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.android.enterprise.connectedapps.annotations.CustomParcelableWrapper;
+import com.google.android.enterprise.connectedapps.internal.Bundler;
+import com.google.android.enterprise.connectedapps.internal.BundlerType;
+
+/**
+ * This parcelable wrapper just passes null to callers.
+ *
+ * <p>It is not functional and only enables use of {@link Handler} for clients
+ * which do not need to actually use the {@link Handler} param or return value.
+ */
+@CustomParcelableWrapper(originalType = Handler.class)
+public final class NullParcelableHandler implements Parcelable {
+
+ /**
+ * Create a wrapper for a given {@link Handler}.
+ */
+ public static <F> NullParcelableHandler of(
+ Bundler bundler, BundlerType type,
+ Handler handler) {
+
+ if (handler != null) {
+ throw new IllegalArgumentException("handler can only be null");
+ }
+
+ return new NullParcelableHandler();
+ }
+
+ private NullParcelableHandler() {
+ }
+
+ public Handler get() {
+ return null;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static final Creator<NullParcelableHandler> CREATOR =
+ new Creator<NullParcelableHandler>() {
+ @Override
+ public NullParcelableHandler createFromParcel(Parcel in) {
+ return new NullParcelableHandler();
+ }
+
+ @Override
+ public NullParcelableHandler[] newArray(int size) {
+ return new NullParcelableHandler[size];
+ }
+ };
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableRemoteDevicePolicyManager.java.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableRemoteDevicePolicyManager.java.txt
index 22217a3..7225c75 100644
--- a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableRemoteDevicePolicyManager.java.txt
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableRemoteDevicePolicyManager.java.txt
@@ -39,6 +39,11 @@
public static <F> NullParcelableRemoteDevicePolicyManager of(
Bundler bundler, BundlerType type,
RemoteDevicePolicyManager remoteDevicePolicyManager) {
+
+ if (remoteDevicePolicyManager != null) {
+ throw new IllegalArgumentException("remoteDevicePolicyManager can only be null");
+ }
+
return new NullParcelableRemoteDevicePolicyManager();
}
diff --git a/common/device-side/bedstead/remoteframeworkclasses/update-apis.sh b/common/device-side/bedstead/remoteframeworkclasses/update-apis.sh
index 04ec558..7a5c6f3 100755
--- a/common/device-side/bedstead/remoteframeworkclasses/update-apis.sh
+++ b/common/device-side/bedstead/remoteframeworkclasses/update-apis.sh
@@ -14,6 +14,6 @@
# limitations under the License.
#
-cp ../../../../../packages/modules/Wifi/framework/api/current.txt src/processor/res/wifi-current.txt
-cp ../../../../../frameworks/base/core/api/current.txt src/processor/res/current.txt
-cp ../../../../../frameworks/base/core/api/test-current.txt src/processor/res/test-current.txt
\ No newline at end of file
+cp ../../../../../packages/modules/Wifi/framework/api/current.txt src/processor/res/apis/wifi-current.txt
+cp ../../../../../frameworks/base/core/api/current.txt src/processor/res/apis/current.txt
+cp ../../../../../frameworks/base/core/api/test-current.txt src/processor/res/apis/test-current.txt
\ No newline at end of file
diff --git a/common/device-side/bedstead/testapp/Android.bp b/common/device-side/bedstead/testapp/Android.bp
index bf92e97..c297949 100644
--- a/common/device-side/bedstead/testapp/Android.bp
+++ b/common/device-side/bedstead/testapp/Android.bp
@@ -99,12 +99,12 @@
java_genrule {
name: "TestApp_Apps",
- srcs: [":EmptyTestApp", ":EmptyTestApp2", ":DeviceAdminTestApp", ":LockTaskApp", ":DelegateTestApp", ":RemoteDPCTestApp", ":SmsApp", ":AccountManagementApp"],
+ srcs: [":EmptyTestApp", ":NotEmptyTestApp", ":DeviceAdminTestApp", ":LockTaskApp", ":DelegateTestApp", ":RemoteDPCTestApp", ":SmsApp", ":AccountManagementApp"],
out: ["TestApp_Apps.res.zip"],
tools: ["soong_zip", "index_testapps", "aapt2"],
cmd: "mkdir -p $(genDir)/res/raw"
+ " && cp $(location :EmptyTestApp) $(genDir)/res/raw"
- + " && cp $(location :EmptyTestApp2) $(genDir)/res/raw"
+ + " && cp $(location :NotEmptyTestApp) $(genDir)/res/raw"
+ " && cp $(location :DeviceAdminTestApp) $(genDir)/res/raw"
+ " && cp $(location :LockTaskApp) $(genDir)/res/raw"
+ " && cp $(location :DelegateTestApp) $(genDir)/res/raw"
@@ -125,11 +125,12 @@
}
android_test_helper_app {
- name: "EmptyTestApp2",
+ name: "NotEmptyTestApp",
static_libs: [
"TestApp_TestApps"
],
- manifest: "manifests/EmptyTestApp2Manifest.xml",
+ manifest: "manifests/NotEmptyTestAppManifest.xml",
+ additional_manifests: ["CommonManifest.xml"],
min_sdk_version: "28"
}
@@ -140,6 +141,7 @@
"DeviceAdminApp"
],
manifest: "manifests/DeviceAdminManifest.xml",
+ additional_manifests: ["CommonManifest.xml"],
min_sdk_version: "28"
}
@@ -149,6 +151,7 @@
"TestApp_TestApps"
],
manifest: "manifests/LockTaskAppManifest.xml",
+ additional_manifests: ["CommonManifest.xml"],
min_sdk_version: "28"
}
@@ -158,6 +161,7 @@
"TestApp_TestApps"
],
manifest: "manifests/DelegateManifest.xml",
+ additional_manifests: ["CommonManifest.xml"],
min_sdk_version: "28"
}
@@ -168,6 +172,7 @@
"DeviceAdminApp"
],
manifest: "manifests/RemoteDPCManifest.xml",
+ additional_manifests: ["CommonManifest.xml"],
min_sdk_version: "28"
}
@@ -177,6 +182,7 @@
"TestApp_TestApps"
],
manifest: "manifests/SmsAppManifest.xml",
+ additional_manifests: ["CommonManifest.xml"],
min_sdk_version: "28"
}
@@ -187,6 +193,7 @@
],
resource_dirs: ["src/testapps/main/res/accountmanagement"],
manifest: "manifests/AccountManagementManifest.xml",
+ additional_manifests: ["CommonManifest.xml"],
min_sdk_version: "28"
}
diff --git a/common/device-side/bedstead/testapp/CommonManifest.xml b/common/device-side/bedstead/testapp/CommonManifest.xml
new file mode 100644
index 0000000..4021535
--- /dev/null
+++ b/common/device-side/bedstead/testapp/CommonManifest.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.TestApp" android:targetSandboxVersion="2">
+
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.READ_SMS" />
+ <uses-permission android:name="android.permission.BODY_SENSORS" />
+ <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.READ_CALENDAR" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <application
+ android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory">
+
+ <activity android:name="android.testapp.activity" android:exported="true" />
+
+ <activity android:name="android.testapp.CrossProfileSharingActivity"
+ android:exported="true">
+ <intent-filter>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <action android:name="com.android.testapp.SOME_ACTION"/>
+ </intent-filter>
+ <!-- Catch ACTION_PICK in case there is no other app handing it-->
+ <intent-filter>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <action android:name="android.intent.action.PICK"/>
+ </intent-filter>
+ </activity>
+
+ <receiver android:name="com.android.bedstead.testapp.TestAppBroadcastController"
+ android:exported="true" />
+ </application>
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
+</manifest>
\ No newline at end of file
diff --git a/common/device-side/bedstead/testapp/manifests/AccountManagementManifest.xml b/common/device-side/bedstead/testapp/manifests/AccountManagementManifest.xml
index 09256e5..2d7bc40 100644
--- a/common/device-side/bedstead/testapp/manifests/AccountManagementManifest.xml
+++ b/common/device-side/bedstead/testapp/manifests/AccountManagementManifest.xml
@@ -20,7 +20,6 @@
package="com.android.bedstead.testapp.AccountManagementApp">
<application
- android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory"
android:testOnly="true">
<service android:name=".TestAppAccountAuthenticatorService" android:exported="true">
diff --git a/common/device-side/bedstead/testapp/manifests/DelegateManifest.xml b/common/device-side/bedstead/testapp/manifests/DelegateManifest.xml
index 5bd3772..6740e5e 100644
--- a/common/device-side/bedstead/testapp/manifests/DelegateManifest.xml
+++ b/common/device-side/bedstead/testapp/manifests/DelegateManifest.xml
@@ -18,13 +18,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.Delegate" android:targetSandboxVersion="2">
-
- <application
- android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory">
-
+ <application>
<!-- Don't allow this test app to be returned by queries unless filtered by package name -->
<meta-data android:name="testapp-package-query-only" android:value="true" />
-
</application>
<uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
</manifest>
\ No newline at end of file
diff --git a/common/device-side/bedstead/testapp/manifests/DeviceAdminManifest.xml b/common/device-side/bedstead/testapp/manifests/DeviceAdminManifest.xml
index 018de06..4ac4d4a 100644
--- a/common/device-side/bedstead/testapp/manifests/DeviceAdminManifest.xml
+++ b/common/device-side/bedstead/testapp/manifests/DeviceAdminManifest.xml
@@ -18,12 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.bedstead.testapp.DeviceAdminTestApp">
- <application
- android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory"
- android:testOnly="true">
-
- <activity android:name="android.testapp.activity" android:exported="true" />
-
+ <application android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory">
<receiver android:name=".DeviceAdminReceiver"
android:permission="android.permission.BIND_DEVICE_ADMIN"
android:exported="true">
diff --git a/common/device-side/bedstead/testapp/manifests/EmptyTestApp2Manifest.xml b/common/device-side/bedstead/testapp/manifests/EmptyTestApp2Manifest.xml
deleted file mode 100644
index 778d8d0..0000000
--- a/common/device-side/bedstead/testapp/manifests/EmptyTestApp2Manifest.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT 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.bedstead.testapp.EmptyTestApp2">
-
- <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES" />
- <uses-permission android:name="android.permission.READ_CONTACTS" />
- <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
- <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
-
- <application
- android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory">
-
- <activity android:name="android.testapp.activity" android:exported="true" />
-
- <activity android:name="android.testapp.CrossProfileSharingActivity"
- android:exported="true">
- <intent-filter>
- <category android:name="android.intent.category.DEFAULT"/>
- <action android:name="com.android.testapp.SOME_ACTION"/>
- </intent-filter>
- <!-- Catch ACTION_PICK in case there is no other app handing it-->
- <intent-filter>
- <category android:name="android.intent.category.DEFAULT"/>
- <action android:name="android.intent.action.PICK"/>
- </intent-filter>
- </activity>
- </application>
- <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
-</manifest>
diff --git a/common/device-side/bedstead/testapp/manifests/EmptyTestAppManifest.xml b/common/device-side/bedstead/testapp/manifests/EmptyTestAppManifest.xml
index ed0290f..1edf39d 100644
--- a/common/device-side/bedstead/testapp/manifests/EmptyTestAppManifest.xml
+++ b/common/device-side/bedstead/testapp/manifests/EmptyTestAppManifest.xml
@@ -18,20 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.bedstead.testapp.EmptyTestApp">
-
- <uses-permission android:name="android.permission.READ_SMS" />
- <uses-permission android:name="android.permission.BODY_SENSORS" />
- <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
- <uses-permission android:name="android.permission.CAMERA" />
- <uses-permission android:name="android.permission.READ_CALENDAR" />
- <uses-permission android:name="android.permission.READ_CONTACTS" />
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
- <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-
- <application
- android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory">
- <meta-data android:name="test-metadata-key" android:value="test-metadata-value" />
+ <application>
</application>
- <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
</manifest>
diff --git a/common/device-side/bedstead/testapp/manifests/LockTaskAppManifest.xml b/common/device-side/bedstead/testapp/manifests/LockTaskAppManifest.xml
index 09685225..ff8ea1c5 100644
--- a/common/device-side/bedstead/testapp/manifests/LockTaskAppManifest.xml
+++ b/common/device-side/bedstead/testapp/manifests/LockTaskAppManifest.xml
@@ -19,7 +19,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.bedstead.testapp.LockTaskApp">
<application
- android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory"
android:testOnly="true">
<activity android:name="android.testapp.ifwhitelistedactivity" android:lockTaskMode="if_whitelisted" android:exported="true" />
diff --git a/common/device-side/bedstead/testapp/manifests/NotEmptyTestAppManifest.xml b/common/device-side/bedstead/testapp/manifests/NotEmptyTestAppManifest.xml
new file mode 100644
index 0000000..2eee159
--- /dev/null
+++ b/common/device-side/bedstead/testapp/manifests/NotEmptyTestAppManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.bedstead.testapp.NotEmptyTestApp">
+
+ <application>
+ <meta-data android:name="test-metadata-key" android:value="test-metadata-value" />
+ </application>
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
+</manifest>
diff --git a/common/device-side/bedstead/testapp/manifests/RemoteDPCManifest.xml b/common/device-side/bedstead/testapp/manifests/RemoteDPCManifest.xml
index 395b5a5..f107562 100644
--- a/common/device-side/bedstead/testapp/manifests/RemoteDPCManifest.xml
+++ b/common/device-side/bedstead/testapp/manifests/RemoteDPCManifest.xml
@@ -18,9 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.RemoteDPC" android:targetSandboxVersion="2">
-
- <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES" />
-
<application
android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory"
android:testOnly="true">
@@ -37,7 +34,6 @@
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
</intent-filter>
</receiver>
-
</application>
<uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
</manifest>
\ No newline at end of file
diff --git a/common/device-side/bedstead/testapp/manifests/SmsAppManifest.xml b/common/device-side/bedstead/testapp/manifests/SmsAppManifest.xml
index 2cbde3c..e68722c 100644
--- a/common/device-side/bedstead/testapp/manifests/SmsAppManifest.xml
+++ b/common/device-side/bedstead/testapp/manifests/SmsAppManifest.xml
@@ -18,11 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.bedstead.testapp.SmsApp">
-
- <uses-permission android:name="android.permission.READ_SMS"/>
-
<application
- android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory"
android:testOnly="true">
<!-- BroadcastReceiver that listens for incoming SMS messages -->
diff --git a/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestApp.java b/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestApp.java
index ac75967..eb4c8aa 100644
--- a/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestApp.java
+++ b/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestApp.java
@@ -143,9 +143,18 @@
return new TestAppInstance(this, user);
}
- private byte[] apkBytes() {
- try (InputStream inputStream =
- sContext.getResources().openRawResource(mDetails.mResourceIdentifier)) {
+ /**
+ * Returns an {@link InputStream} of the apk for this app.
+ */
+ public InputStream apkStream() {
+ return sContext.getResources().openRawResource(mDetails.mResourceIdentifier);
+ }
+
+ /**
+ * Returns a byte array of the apk for this app.
+ */
+ public byte[] apkBytes() {
+ try (InputStream inputStream = apkStream()) {
return readInputStreamFully(inputStream);
} catch (IOException e) {
throw new NeneException("Error when reading TestApp bytes", e);
diff --git a/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppInstance.java b/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppInstance.java
index 68cf755..fb5b505 100644
--- a/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppInstance.java
+++ b/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppInstance.java
@@ -150,19 +150,30 @@
* will have the same effect as calling {@link #keepAlive()}.
*/
public void registerReceiver(IntentFilter intentFilter) {
+ registerReceiver(intentFilter, 0);
+ }
+
+ /**
+ * See {@link registerReceiver(IntentFilter)}.
+ */
+ public void registerReceiver(IntentFilter intentFilter, int flags) {
if (mRegisteredBroadcastReceivers.containsKey(intentFilter)) {
return;
}
long receiverId = UUID.randomUUID().getMostSignificantBits();
- registerReceiver(intentFilter, receiverId);
+ registerReceiver(intentFilter, receiverId, flags);
keepAlive(/* manualKeepAlive= */ false);
}
private void registerReceiver(IntentFilter intentFilter, long receiverId) {
+ registerReceiver(intentFilter, receiverId, 0);
+ }
+
+ private void registerReceiver(IntentFilter intentFilter, long receiverId, int flags) {
try {
mConnector.connect();
- mTestAppController.other().registerReceiver(receiverId, intentFilter);
+ mTestAppController.other().registerReceiver(receiverId, intentFilter, flags);
mRegisteredBroadcastReceivers.put(intentFilter, receiverId);
} catch (UnavailableProfileException e) {
throw new IllegalStateException("Could not connect to test app", e);
diff --git a/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppQueryBuilder.java b/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppQueryBuilder.java
index 5afc802..b6e2531 100644
--- a/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppQueryBuilder.java
+++ b/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppQueryBuilder.java
@@ -48,6 +48,7 @@
new SetQueryHelper<>(this);
SetQueryHelper<TestAppQueryBuilder, ServiceInfo, ServiceQuery<?>> mServices =
new SetQueryHelper<>(this);
+ BooleanQueryHelper<TestAppQueryBuilder> mIsDeviceAdmin = new BooleanQueryHelper<>(this);
StringQueryHelper<TestAppQueryBuilder> mSharedUserId = new StringQueryHelper<>(this);
TestAppQueryBuilder(TestAppProvider provider) {
@@ -111,6 +112,13 @@
}
/**
+ * Query for an app which is a device admin.
+ */
+ public BooleanQuery<TestAppQueryBuilder> whereIsDeviceAdmin() {
+ return mIsDeviceAdmin;
+ }
+
+ /**
* Query for a {@link TestApp} by its sharedUserId;
*/
public StringQuery<TestAppQueryBuilder> whereSharedUserId() {
@@ -194,6 +202,13 @@
return false;
}
+ // TODO(b/198419895): Actually query for the correct receiver + metadata
+ boolean isDeviceAdmin = details.mApp.getPackageName().equals(
+ "com.android.bedstead.testapp.DeviceAdminTestApp");
+ if (!BooleanQueryHelper.matches(mIsDeviceAdmin, isDeviceAdmin)) {
+ return false;
+ }
+
if (mSharedUserId.isEmpty()) {
if (details.sharedUserId() != null) {
return false;
@@ -226,7 +241,8 @@
mServices.describeQuery("services"),
mPermissions.describeQuery("permissions"),
mSharedUserId.describeQuery("sharedUserId"),
- mTestOnly.describeQuery("testOnly")
+ mTestOnly.describeQuery("testOnly"),
+ mIsDeviceAdmin.describeQuery("isDeviceAdmin")
) + "}";
}
}
diff --git a/common/device-side/bedstead/testapp/src/test/java/com/android/bedstead/testapp/TestAppProviderTest.java b/common/device-side/bedstead/testapp/src/test/java/com/android/bedstead/testapp/TestAppProviderTest.java
index bc270b4..3718237 100644
--- a/common/device-side/bedstead/testapp/src/test/java/com/android/bedstead/testapp/TestAppProviderTest.java
+++ b/common/device-side/bedstead/testapp/src/test/java/com/android/bedstead/testapp/TestAppProviderTest.java
@@ -234,6 +234,26 @@
}
@Test
+ public void query_isDeviceAdmin_returnsMatching() {
+ TestApp testApp = mTestAppProvider.query()
+ .whereIsDeviceAdmin().isTrue()
+ .get();
+
+ assertThat(testApp.packageName()).isEqualTo(
+ "com.android.bedstead.testapp.DeviceAdminTestApp");
+ }
+
+ @Test
+ public void query_isNotDeviceAdmin_returnsMatching() {
+ TestApp testApp = mTestAppProvider.query()
+ .whereIsDeviceAdmin().isFalse()
+ .get();
+
+ assertThat(testApp.packageName()).isNotEqualTo(
+ "com.android.bedstead.testapp.DeviceAdminTestApp");
+ }
+
+ @Test
public void query_doesNotSpecifySharedUserId_sharedUserIdIsNull() {
TestApp testApp = mTestAppProvider.query()
.get();
diff --git a/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppBroadcastController.java b/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppBroadcastController.java
new file mode 100644
index 0000000..554567c
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppBroadcastController.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.testapp;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.compatibility.common.util.enterprise.DeviceAdminReceiverUtils;
+
+/**
+ * {@link BroadcastReceiver} allowing for simple control of test apps using broadcasts.
+ */
+public final class TestAppBroadcastController extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ setResultCode(Activity.RESULT_OK);
+ if (DeviceAdminReceiverUtils.disableSelf(context, intent)) return;
+ }
+}
diff --git a/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppController.java b/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppController.java
index 06c5c1e..08113b0 100644
--- a/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppController.java
+++ b/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppController.java
@@ -39,10 +39,19 @@
*/
@CrossUser
public void registerReceiver(Context context, long receiverId, IntentFilter intentFilter) {
+ registerReceiver(context, receiverId, intentFilter, 0);
+ }
+
+ /**
+ * See {@link registerReceiver(Context, long, IntentFilter)}.
+ */
+ @CrossUser
+ public void registerReceiver(Context context, long receiverId, IntentFilter intentFilter,
+ int flags) {
BaseTestAppBroadcastReceiver broadcastReceiver = new BaseTestAppBroadcastReceiver();
sBroadcastReceivers.put(receiverId, broadcastReceiver);
- context.registerReceiver(broadcastReceiver, intentFilter);
+ context.registerReceiver(broadcastReceiver, intentFilter, flags);
}
/**
diff --git a/common/device-side/util-axt/Android.bp b/common/device-side/util-axt/Android.bp
index 5357ce5..11f851b 100644
--- a/common/device-side/util-axt/Android.bp
+++ b/common/device-side/util-axt/Android.bp
@@ -49,6 +49,5 @@
name: "compatibility-device-util-nodeps",
srcs: [
"src/com/android/compatibility/common/util/IBinderParcelable.java",
- "src/com/android/compatibility/common/util/ImeAwareEditText.java",
],
}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java
index 49208b1..3655fb0 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java
@@ -105,6 +105,12 @@
addSplitFromNonDangerousPermissions(packagesToVerify, pregrantUidStates);
}
+ if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)
+ || ApiLevelUtil.codenameStartsWith("T")) {
+ addImplicitlyGrantedPermission(Manifest.permission.POST_NOTIFICATIONS,
+ Build.VERSION_CODES.TIRAMISU, packagesToVerify, pregrantUidStates);
+ }
+
// Add exceptions
addExceptionsDefaultPermissions(packagesToVerify, runtimePermNames, pregrantUidStates);
@@ -507,6 +513,37 @@
}
}
+ /**
+ * Add a permission which is granted, if it is implicitly added to a package
+ * @param permissionToAdd The permission to be added
+ * @param targetSdk The targetSDK below which the permission is added in
+ * @param packageInfos The packageInfos to be checked
+ * @param outUidStates The state to be modified
+ */
+ public static void addImplicitlyGrantedPermission(String permissionToAdd, int targetSdk,
+ Map<String, PackageInfo> packageInfos, SparseArray<UidState> outUidStates) {
+ for (PackageInfo pkg : packageInfos.values()) {
+ if (pkg.applicationInfo.targetSdkVersion >= targetSdk) {
+ continue;
+ }
+ for (String perm: pkg.requestedPermissions) {
+ if (perm.equals(permissionToAdd)) {
+ int uid = pkg.applicationInfo.uid;
+ UidState uidState = outUidStates.get(uid);
+ if (uidState != null
+ && uidState.grantedPermissions.containsKey(permissionToAdd)) {
+ // permission is already granted. Don't override the grant-state.
+ continue;
+ }
+
+ appendPackagePregrantedPerms(pkg, "permission " + permissionToAdd
+ + " is granted to pre-" + targetSdk + " apps", false,
+ Collections.singleton(permissionToAdd), outUidStates);
+ }
+ }
+ }
+ }
+
public static void appendPackagePregrantedPerms(PackageInfo packageInfo, String reason,
boolean fixed, Set<String> pregrantedPerms, SparseArray<UidState> outUidStates) {
final int uid = packageInfo.applicationInfo.uid;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
index 6d44391..b68578f 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
@@ -19,11 +19,10 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import androidx.annotation.Nullable;
import android.util.Log;
-import java.util.ArrayList;
-import java.util.List;
+import androidx.annotation.Nullable;
+
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
@@ -78,25 +77,33 @@
return create(context, intentFilter, /* checker= */ null);
}
- public static BlockingBroadcastReceiver create(Context context, String expectedAction, Function<Intent, Boolean> checker) {
+ public static BlockingBroadcastReceiver create(Context context, String expectedAction,
+ Function<Intent, Boolean> checker) {
return create(context, new IntentFilter(expectedAction), checker);
}
- public static BlockingBroadcastReceiver create(Context context, IntentFilter intentFilter, Function<Intent, Boolean> checker) {
+ public static BlockingBroadcastReceiver create(Context context, IntentFilter intentFilter,
+ Function<Intent, Boolean> checker) {
return create(context, Set.of(intentFilter), checker);
}
- public static BlockingBroadcastReceiver create(Context context, Set<IntentFilter> intentFilters) {
+ public static BlockingBroadcastReceiver create(Context context,
+ Set<IntentFilter> intentFilters) {
return create(context, intentFilters, /* checker= */ null);
}
- public static BlockingBroadcastReceiver create(Context context, Set<IntentFilter> intentFilters, Function<Intent, Boolean> checker) {
+ public static BlockingBroadcastReceiver create(Context context, Set<IntentFilter> intentFilters,
+ Function<Intent, Boolean> checker) {
BlockingBroadcastReceiver blockingBroadcastReceiver =
new BlockingBroadcastReceiver(context, intentFilters, checker);
return blockingBroadcastReceiver;
}
+ public BlockingBroadcastReceiver(Context context) {
+ this(context, Set.of());
+ }
+
public BlockingBroadcastReceiver(Context context, String expectedAction) {
this(context, new IntentFilter(expectedAction));
}
@@ -143,7 +150,8 @@
public BlockingBroadcastReceiver register() {
for (IntentFilter intentFilter : mIntentFilters) {
- mContext.registerReceiver(this, intentFilter);
+ mContext.registerReceiver(this, intentFilter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
}
return this;
@@ -153,7 +161,8 @@
for (IntentFilter intentFilter : mIntentFilters) {
mContext.registerReceiverForAllUsers(
this, intentFilter, /* broadcastPermission= */ null,
- /* scheduler= */ null);
+ /* scheduler= */ null,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
}
return this;
@@ -173,7 +182,8 @@
* Wait until the broadcast and return the received broadcast intent. {@code null} is returned
* if no broadcast with expected action is received within 60 seconds.
*/
- public @Nullable Intent awaitForBroadcast() {
+ public @Nullable
+ Intent awaitForBroadcast() {
return awaitForBroadcast(DEFAULT_TIMEOUT_SECONDS * 1000);
}
@@ -193,7 +203,8 @@
* Wait until the broadcast and return the received broadcast intent. {@code null} is returned
* if no broadcast with expected action is received within the given timeout.
*/
- public @Nullable Intent awaitForBroadcast(long timeoutMillis) {
+ public @Nullable
+ Intent awaitForBroadcast(long timeoutMillis) {
if (mReceivedIntent != null) {
return mReceivedIntent;
}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BroadcastMessenger.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BroadcastMessenger.java
new file mode 100644
index 0000000..a5a12a4
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BroadcastMessenger.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.util;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * Provides a one-way communication mechanism using a Parcelable as a payload, via broadcasts.
+ *
+ * Use {@link #send(Context, String, Parcelable)} to send a message.
+ * Use {@link Receiver} to receive a message.
+ *
+ * Pick a unique "suffix" for your test, and use it with both the sender and receiver, in order
+ * to avoid "cross-talks" between different tests. (if they ever run at the same time.)
+ */
+public final class BroadcastMessenger {
+ private static final String TAG = "BroadcastMessenger";
+
+ private static final String ACTION_MESSAGE =
+ "com.android.compatibility.common.util.BroadcastMessenger.ACTION_MESSAGE_";
+ private static final String ACTION_PING =
+ "com.android.compatibility.common.util.BroadcastMessenger.ACTION_PING_";
+ private static final String EXTRA_MESSAGE =
+ "com.android.compatibility.common.util.BroadcastMessenger.EXTRA_MESSAGE";
+
+ /**
+ * We need to drop messages that were sent before the receiver was created. We keep
+ * track of the message send time in this extra.
+ */
+ private static final String EXTRA_SENT_TIME =
+ "com.android.compatibility.common.util.BroadcastMessenger.EXTRA_SENT_TIME";
+
+ public static final int DEFAULT_TIMEOUT_MS = 10_000;
+
+ private static long getCurrentTime() {
+ return SystemClock.uptimeMillis();
+ }
+
+ private static void sendBroadcast(@NonNull Intent i, @NonNull Context context,
+ @NonNull String broadcastSuffix, @Nullable String receiverPackage) {
+ i.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ i.setPackage(receiverPackage);
+ i.putExtra(EXTRA_SENT_TIME, getCurrentTime());
+
+ context.sendBroadcast(i);
+ }
+
+ /** Send a message to the {@link Receiver} expecting a given "suffix". */
+ public static <T extends Parcelable> void send(@NonNull Context context,
+ @NonNull String broadcastSuffix, @NonNull T message) {
+ final Intent i = new Intent(ACTION_MESSAGE + Objects.requireNonNull(broadcastSuffix));
+ i.putExtra(EXTRA_MESSAGE, Objects.requireNonNull(message));
+
+ Log.i(TAG, "Sending: " + message);
+ sendBroadcast(i, context, broadcastSuffix, /*receiverPackage=*/ null);
+ }
+
+ private static void sendPing(@NonNull Context context, @NonNull String broadcastSuffix,
+ @NonNull String receiverPackage) {
+ final Intent i = new Intent(ACTION_PING + Objects.requireNonNull(broadcastSuffix));
+
+ Log.i(TAG, "Sending a ping");
+ sendBroadcast(i, context, broadcastSuffix, receiverPackage);
+ }
+
+ /**
+ * Receive messages sent with {@link #send}. Note it'll ignore all the messages that were
+ * sent before instantiated.
+ *
+ * @param <T> the class that encapsulates the message.
+ */
+ public static final class Receiver<T extends Parcelable> implements AutoCloseable {
+ private final Context mContext;
+ private final String mBroadcastSuffix;
+ private final HandlerThread mReceiverThread = new HandlerThread(TAG);
+ private final Handler mReceiverHandler;
+
+ @GuardedBy("mMessages")
+ private final ArrayList<T> mMessages = new ArrayList<>();
+ private final long mCreatedTime = getCurrentTime();
+ private boolean mRegistered;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Log.d(TAG, "Received intent: " + intent);
+ if (intent.getAction().equals(ACTION_MESSAGE + mBroadcastSuffix)
+ || intent.getAction().equals(ACTION_PING + mBroadcastSuffix)) {
+ // OK
+ } else {
+ throw new RuntimeException("Unknown broadcast received: " + intent);
+ }
+ if (intent.getLongExtra(EXTRA_SENT_TIME, 0) < mCreatedTime) {
+ Log.i(TAG, "Dropping stale broadcast: " + intent);
+ return;
+ }
+
+ // Note for a PING, the message will be null.
+ final T message = intent.getParcelableExtra(EXTRA_MESSAGE);
+ if (message != null) {
+ Log.i(TAG, "Received: " + message);
+ }
+
+ synchronized (mMessages) {
+ mMessages.add(message);
+ mMessages.notifyAll();
+ }
+ }
+ };
+
+ /**
+ * Constructor.
+ */
+ public Receiver(@NonNull Context context, @NonNull String broadcastSuffix) {
+ mContext = context;
+ mBroadcastSuffix = Objects.requireNonNull(broadcastSuffix);
+
+ mReceiverThread.start();
+ mReceiverHandler = new Handler(mReceiverThread.getLooper());
+
+ final IntentFilter fi = new IntentFilter(ACTION_MESSAGE + mBroadcastSuffix);
+ fi.addAction(ACTION_PING + mBroadcastSuffix);
+
+ context.registerReceiver(mReceiver, fi, /* permission=*/ null,
+ mReceiverHandler, Context.RECEIVER_EXPORTED);
+ mRegistered = true;
+ }
+
+ @Override
+ public void close() {
+ if (mRegistered) {
+ mContext.unregisterReceiver(mReceiver);
+ mReceiverThread.quit();
+ mRegistered = false;
+ }
+ }
+
+ /**
+ * Receive the next message with a 10 second timeout.
+ */
+ @NonNull
+ public T waitForNextMessage() {
+ return waitForNextMessage(DEFAULT_TIMEOUT_MS);
+ }
+
+ /**
+ * Receive the next message.
+ */
+ @NonNull
+ public T waitForNextMessage(long timeoutMillis) {
+ final T message = waitForNextMessageOrPing(timeoutMillis);
+ if (message == null) {
+ throw new RuntimeException("Received unexpected ACTION_PING");
+ }
+ return message;
+ }
+
+ /**
+ * Internal method, either return the next message, or null when a PING broadcast
+ * is received.
+ */
+ @Nullable
+ private T waitForNextMessageOrPing(long timeoutMillis) {
+ final long timeout = System.currentTimeMillis() + timeoutMillis;
+ synchronized (mMessages) {
+ while (mMessages.size() == 0) {
+ final long wait = timeout - System.currentTimeMillis();
+ if (wait <= 0) {
+ throw new RuntimeException("Timeout waiting for the next message");
+ }
+ try {
+ mMessages.wait(wait);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return mMessages.remove(0);
+ }
+ }
+
+ /**
+ * Ensure that no further messages have been received.
+ *
+ * Call it before {@link #close()}.
+ */
+ public void ensureNoMoreMessages() {
+ // If there's a message already in mMessages, then we know it'll fail, so we don't
+ // need to send a ping.
+ // OTOH, even if there's no message enqueued, there may be broadcasts already enqueued,
+ // so we send a "ping" message,
+ synchronized (mMessages) {
+ if (mMessages.size() == 0) {
+ // Send a ping to myself.
+ sendPing(mContext, mBroadcastSuffix, mContext.getPackageName());
+ }
+ }
+
+ final T m = waitForNextMessageOrPing(DEFAULT_TIMEOUT_MS);
+ if (m == null) {
+ return; // Okay. Ping will deliver a null message.
+ }
+ throw new RuntimeException("No more messages expected, but received: " + m);
+ }
+ }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/CallbackAsserter.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/CallbackAsserter.java
index e722fb2..9c27e23 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/CallbackAsserter.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/CallbackAsserter.java
@@ -101,7 +101,8 @@
mLatch.countDown();
}
};
- InstrumentationRegistry.getContext().registerReceiver(mReceiver, filter);
+ InstrumentationRegistry.getContext().registerReceiver(mReceiver, filter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
}
@Override
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/ImeAwareEditText.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/ImeAwareEditText.java
deleted file mode 100644
index f28d085..0000000
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/ImeAwareEditText.java
+++ /dev/null
@@ -1,92 +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.util;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-
-public class ImeAwareEditText extends EditText {
- private boolean mHasPendingShowSoftInputRequest;
- final Runnable mRunShowSoftInputIfNecessary = () -> showSoftInputIfNecessary();
-
- public ImeAwareEditText(Context context) {
- super(context, null);
- }
-
- public ImeAwareEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public ImeAwareEditText(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public ImeAwareEditText(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- /**
- * This method is called back by the system when the system is about to establish a connection
- * to the current input method.
- *
- * <p>This is a good and reliable signal to schedule a pending task to call
- * {@link InputMethodManager#showSoftInput(View, int)}.</p>
- *
- * @param editorInfo context about the text input field.
- * @return {@link InputConnection} to be passed to the input method.
- */
- @Override
- public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
- final InputConnection ic = super.onCreateInputConnection(editorInfo);
- if (mHasPendingShowSoftInputRequest) {
- removeCallbacks(mRunShowSoftInputIfNecessary);
- post(mRunShowSoftInputIfNecessary);
- }
- return ic;
- }
-
- private void showSoftInputIfNecessary() {
- if (mHasPendingShowSoftInputRequest) {
- final InputMethodManager imm =
- getContext().getSystemService(InputMethodManager.class);
- imm.showSoftInput(this, 0);
- mHasPendingShowSoftInputRequest = false;
- }
- }
-
- public void scheduleShowSoftInput() {
- final InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
- if (imm.hasActiveInputConnection(this)) {
- // This means that ImeAwareEditText is already connected to the IME.
- // InputMethodManager#showSoftInput() is guaranteed to pass client-side focus check.
- mHasPendingShowSoftInputRequest = false;
- removeCallbacks(mRunShowSoftInputIfNecessary);
- imm.showSoftInput(this, 0);
- return;
- }
-
- // Otherwise, InputMethodManager#showSoftInput() should be deferred after
- // onCreateInputConnection().
- mHasPendingShowSoftInputRequest = true;
- }
-}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java
index 639c871..b344154 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java
@@ -98,9 +98,15 @@
} else {
Rect boundsBeforeScroll = scrollable.getBounds();
boolean scrollAtStartOrEnd = !scrollable.scrollForward();
- Rect boundsAfterScroll = scrollable.getBounds();
- isAtEnd = scrollAtStartOrEnd && boundsBeforeScroll.equals(
- boundsAfterScroll);
+ // The scrollable view may no longer be scrollable after the toolbar is
+ // collapsed.
+ if (scrollable.exists()) {
+ Rect boundsAfterScroll = scrollable.getBounds();
+ isAtEnd = scrollAtStartOrEnd && boundsBeforeScroll.equals(
+ boundsAfterScroll);
+ } else {
+ isAtEnd = scrollAtStartOrEnd;
+ }
}
} else {
// There might be a collapsing toolbar, but no scrollable view. Try to collapse
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
index b6473b6..60d7535 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
@@ -163,7 +163,8 @@
}
public void register() {
- mContext.registerReceiver(this, new IntentFilter(mAction));
+ mContext.registerReceiver(this, new IntentFilter(mAction),
+ Context.RECEIVER_EXPORTED_UNAUDITED);
}
public boolean await() throws InterruptedException {
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceAdminReceiverUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceAdminReceiverUtils.java
index d1d7dcb..c0c434e 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceAdminReceiverUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceAdminReceiverUtils.java
@@ -29,7 +29,7 @@
private static final String TAG = DeviceAdminReceiverUtils.class.getSimpleName();
private static final boolean DEBUG = false;
- private static final String ACTION_DISABLE_SELF = "disable_self";
+ public static final String ACTION_DISABLE_SELF = "disable_self";
/**
* Disables itself as profile / owner upon receiving a {@value #ACTION_DISABLE_SELF} intent.
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS
index cf88726..19b6194 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS
@@ -1,2 +1,2 @@
# Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204
-file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
\ No newline at end of file
+file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
diff --git a/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java b/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java
index f1c053f..eb7bf3b 100644
--- a/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java
+++ b/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.angleIntegrationTest.common;
+package com.android.angleintegrationtest.common;
import static javax.microedition.khronos.egl.EGL10.EGL_STENCIL_SIZE;
@@ -23,6 +23,7 @@
import android.opengl.GLES20;
import android.os.Build.VERSION_CODES;
import android.util.Log;
+
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
diff --git a/hostsidetests/angle/app/driverTest/AndroidManifest.xml b/hostsidetests/angle/app/driverTest/AndroidManifest.xml
index 73392df..6fe6704 100755
--- a/hostsidetests/angle/app/driverTest/AndroidManifest.xml
+++ b/hostsidetests/angle/app/driverTest/AndroidManifest.xml
@@ -16,13 +16,13 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.angleIntegrationTest.driverTest"
+ package="com.android.angleintegrationtest.drivertest"
android:targetSandboxVersion="2">
<application android:debuggable="true">
<uses-library android:name="android.test.runner"/>
- <activity android:name="com.android.angleIntegrationTest.common.AngleIntegrationTestActivity"
+ <activity android:name="com.android.angleintegrationtest.common.AngleIntegrationTestActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -32,6 +32,6 @@
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.angleIntegrationTest.driverTest"/>
+ android:targetPackage="com.android.angleintegrationtest.drivertest"/>
</manifest>
diff --git a/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java b/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java
index 450461a..6eb169b 100644
--- a/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java
+++ b/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.angleIntegrationTest.driverTest;
+package com.android.angleintegrationtest.drivertest;
import static org.junit.Assert.fail;
import androidx.test.runner.AndroidJUnit4;
-import com.android.angleIntegrationTest.common.GlesView;
+import com.android.angleintegrationtest.common.GlesView;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,7 +46,6 @@
@Test
public void testUseDefaultDriver() throws Exception {
- // The rules file does not enable ANGLE for this app
validateDeveloperOption(false);
}
diff --git a/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml b/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml
index e88a8c3..70dcec4 100755
--- a/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml
+++ b/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml
@@ -16,13 +16,13 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.angleIntegrationTest.driverTestSecondary"
+ package="com.android.angleintegrationtest.drivertestsecondary"
android:targetSandboxVersion="2">
<application android:debuggable="true">
<uses-library android:name="android.test.runner"/>
- <activity android:name="com.android.angleIntegrationTest.common.AngleIntegrationTestActivity"
+ <activity android:name="com.android.angleintegrationtest.common.AngleIntegrationTestActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -32,6 +32,6 @@
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.angleIntegrationTest.driverTestSecondary"/>
+ android:targetPackage="com.android.angleintegrationtest.drivertestsecondary"/>
</manifest>
diff --git a/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.java b/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.java
index 7b25c39..b855f6b 100644
--- a/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.java
+++ b/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.java
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.angleIntegrationTest.driverTestSecondary;
+package com.android.angleintegrationtest.drivertestsecondary;
import static org.junit.Assert.fail;
import androidx.test.runner.AndroidJUnit4;
-import com.android.angleIntegrationTest.common.GlesView;
+import com.android.angleintegrationtest.common.GlesView;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,7 +46,6 @@
@Test
public void testUseDefaultDriver() throws Exception {
- // The rules file does not enable ANGLE for this app
validateDeveloperOption(false);
}
diff --git a/hostsidetests/angle/app/gameDriverTest/Android.bp b/hostsidetests/angle/app/gameDriverTest/Android.bp
new file mode 100644
index 0000000..749f8cf
--- /dev/null
+++ b/hostsidetests/angle/app/gameDriverTest/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAngleGameDriverTestCases",
+ defaults: ["cts_support_defaults"],
+ srcs: [
+ "src/**/*.java",
+ ],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ compile_multilib: "both",
+ static_libs: [
+ "ctstestrunner-axt",
+ "androidx.test.rules",
+ "AngleIntegrationTestCommon",
+ ],
+}
diff --git a/hostsidetests/angle/app/gameDriverTest/AndroidManifest.xml b/hostsidetests/angle/app/gameDriverTest/AndroidManifest.xml
new file mode 100755
index 0000000..7b6b10a
--- /dev/null
+++ b/hostsidetests/angle/app/gameDriverTest/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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.angleintegrationtest.gamedrivertest"
+ android:targetSandboxVersion="2">
+
+ <application android:debuggable="true"
+ android:appCategory="game">
+ <uses-library android:name="android.test.runner"/>
+
+ <activity android:name="com.android.angleintegrationtest.common.AngleIntegrationTestActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.angleintegrationtest.gamedrivertest"/>
+
+</manifest>
diff --git a/hostsidetests/angle/app/gameDriverTest/src/com/android/angleIntegrationTest/gameDriverTest/AngleDriverTestActivity.java b/hostsidetests/angle/app/gameDriverTest/src/com/android/angleIntegrationTest/gameDriverTest/AngleDriverTestActivity.java
new file mode 100644
index 0000000..cec4d2a
--- /dev/null
+++ b/hostsidetests/angle/app/gameDriverTest/src/com/android/angleIntegrationTest/gameDriverTest/AngleDriverTestActivity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.angleintegrationtest.gamedrivertest;
+
+import static org.junit.Assert.fail;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.angleintegrationtest.common.GlesView;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AngleDriverTestActivity {
+
+ private final String mTAG = this.getClass().getSimpleName();
+
+ private void validateDeveloperOption(boolean angleEnabled) throws Exception {
+ GlesView glesView = new GlesView();
+
+ if (!glesView.validateDeveloperOption(angleEnabled)) {
+ if (angleEnabled) {
+ String renderer = glesView.getRenderer();
+ fail("Failure - ANGLE was not loaded: '" + renderer + "'");
+ } else {
+ String renderer = glesView.getRenderer();
+ fail("Failure - ANGLE was loaded: '" + renderer + "'");
+ }
+ }
+ }
+
+ @Test
+ public void testUseDefaultDriver() throws Exception {
+ validateDeveloperOption(false);
+ }
+
+ @Test
+ public void testUseAngleDriver() throws Exception {
+ validateDeveloperOption(true);
+ }
+
+ @Test
+ public void testUseNativeDriver() throws Exception {
+ validateDeveloperOption(false);
+ }
+}
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
index ba33966..0580d3a 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
@@ -36,23 +36,20 @@
// System Properties
static final String PROPERTY_TEMP_RULES_FILE = "debug.angle.rules";
- // Rules File
- static final String DEVICE_TEMP_RULES_FILE_DIRECTORY = "/data/local/tmp";
- static final String DEVICE_TEMP_RULES_FILE_FILENAME = "a4a_rules.json";
- static final String DEVICE_TEMP_RULES_FILE_PATH =
- DEVICE_TEMP_RULES_FILE_DIRECTORY + "/" + DEVICE_TEMP_RULES_FILE_FILENAME;
-
// ANGLE
static final String ANGLE_PACKAGE_NAME = "com.android.angle";
- static final String ANGLE_DRIVER_TEST_PKG = "com.android.angleIntegrationTest.driverTest";
+ static final String ANGLE_DRIVER_TEST_PKG = "com.android.angleintegrationtest.drivertest";
static final String ANGLE_DRIVER_TEST_SEC_PKG =
- "com.android.angleIntegrationTest.driverTestSecondary";
+ "com.android.angleintegrationtest.drivertestsecondary";
+ static final String ANGLE_GAME_DRIVER_TEST_PKG =
+ "com.android.angleintegrationtest.gamedrivertest";
static final String ANGLE_DRIVER_TEST_CLASS = "AngleDriverTestActivity";
static final String ANGLE_DRIVER_TEST_DEFAULT_METHOD = "testUseDefaultDriver";
static final String ANGLE_DRIVER_TEST_ANGLE_METHOD = "testUseAngleDriver";
static final String ANGLE_DRIVER_TEST_NATIVE_METHOD = "testUseNativeDriver";
static final String ANGLE_DRIVER_TEST_APP = "CtsAngleDriverTestCases.apk";
static final String ANGLE_DRIVER_TEST_SEC_APP = "CtsAngleDriverTestCasesSecondary.apk";
+ static final String ANGLE_GAME_DRIVER_TEST_APP = "CtsAngleGameDriverTestCases.apk";
static final String ANGLE_DUMPSYS_GPU_TEST_PKG =
"com.android.angleintegrationtest.dumpsysgputest";
static final String ANGLE_DUMPSYS_GPU_TEST_CLASS = "AngleDumpsysGpuTestActivity";
@@ -145,6 +142,26 @@
device.executeShellCommand("setprop " + property + " " + value);
}
+ static void setGameModeBatteryConfig(ITestDevice device, String packageName, boolean useAngle)
+ throws Exception {
+ device.executeShellCommand("device_config put game_overlay " + packageName
+ + " mode=3,useAngle=" + Boolean.toString(useAngle));
+ }
+
+ static void setGameModeStandardConfig(ITestDevice device, String packageName, boolean useAngle)
+ throws Exception {
+ device.executeShellCommand("device_config put game_overlay " + packageName
+ + " mode=1,useAngle=" + Boolean.toString(useAngle));
+ }
+
+ static void setGameModeBattery(ITestDevice device, String packageName) throws Exception {
+ device.executeShellCommand("cmd game mode battery " + packageName);
+ }
+
+ static void setGameModeStandard(ITestDevice device, String packageName) throws Exception {
+ device.executeShellCommand("cmd game mode standard " + packageName);
+ }
+
/**
* Find and parse the `dumpsys gpu` output for the specified package.
*
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
index 790db4d..98f81b9 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
@@ -88,6 +88,7 @@
stopPackage(getDevice(), ANGLE_DRIVER_TEST_PKG);
stopPackage(getDevice(), ANGLE_DRIVER_TEST_SEC_PKG);
+ stopPackage(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG);
}
@After
@@ -354,6 +355,98 @@
}
/**
+ * Test ANGLE is loaded when the Battery Game Mode includes 'useAngle=true'.
+ */
+ @Test
+ public void testGameModeBatteryUseAngleDriver() throws Exception {
+ Assume.assumeTrue(isAngleInstalled(getDevice()));
+ Assume.assumeFalse(isNativeDriverAngle(getDevice()));
+
+ installApp(ANGLE_GAME_DRIVER_TEST_APP);
+
+ setGameModeBatteryConfig(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG, true);
+ setGameModeBattery(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG);
+
+ runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG,
+ ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
+ ANGLE_DRIVER_TEST_ANGLE_METHOD);
+ }
+
+ /**
+ * Test ANGLE is loaded when the Standard Game Mode includes 'useAngle=true'.
+ */
+ @Test
+ public void testGameModeStandardUseAngleDriver() throws Exception {
+ Assume.assumeTrue(isAngleInstalled(getDevice()));
+ Assume.assumeFalse(isNativeDriverAngle(getDevice()));
+
+ installApp(ANGLE_GAME_DRIVER_TEST_APP);
+
+ setGameModeStandardConfig(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG, true);
+ setGameModeStandard(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG);
+
+ runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG,
+ ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
+ ANGLE_DRIVER_TEST_ANGLE_METHOD);
+ }
+
+ /**
+ * Test setting the Game Mode to use ANGLE ('useAngle=true') and then overriding that to use the
+ * native driver with the Global.Settings loads the native driver.
+ */
+ @Test
+ public void testGameModeBatteryUseAngleOverrideWithNative() throws Exception {
+ Assume.assumeTrue(isAngleInstalled(getDevice()));
+ Assume.assumeFalse(isNativeDriverAngle(getDevice()));
+
+ installApp(ANGLE_GAME_DRIVER_TEST_APP);
+
+ // Set Game Mode to use ANGLE and verify ANGLE is loaded.
+ setGameModeBatteryConfig(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG, true);
+ setGameModeBattery(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG);
+
+ runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG,
+ ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
+ ANGLE_DRIVER_TEST_ANGLE_METHOD);
+
+ // Set Global.Settings to use the native driver and verify the native driver is loaded.
+ setAndValidateAngleDevOptionPkgDriver(ANGLE_GAME_DRIVER_TEST_PKG,
+ sDriverGlobalSettingMap.get(OpenGlDriverChoice.NATIVE));
+
+ runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG,
+ ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
+ ANGLE_DRIVER_TEST_NATIVE_METHOD);
+ }
+
+ /**
+ * Test setting the Game Mode to not use ANGLE ('useAngle=false') and then overriding that to
+ * use ANGLE with the Global.Settings loads ANGLE.
+ */
+ @Test
+ public void testGameModeBatteryDontUseAngleOverrideWithAngle() throws Exception {
+ Assume.assumeTrue(isAngleInstalled(getDevice()));
+ Assume.assumeFalse(isNativeDriverAngle(getDevice()));
+
+ installApp(ANGLE_GAME_DRIVER_TEST_APP);
+
+ // Set Game Mode to *not* use ANGLE and verify the native driver is loaded.
+ setGameModeBatteryConfig(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG, false);
+ setGameModeBattery(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG);
+
+ runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG,
+ ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
+ ANGLE_DRIVER_TEST_NATIVE_METHOD);
+
+ // Set Global.Settings to use ANGLE and verify ANGLE is loaded.
+ setAndValidateAngleDevOptionPkgDriver(ANGLE_GAME_DRIVER_TEST_PKG,
+ sDriverGlobalSettingMap.get(OpenGlDriverChoice.ANGLE));
+
+ runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG,
+ ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
+ ANGLE_DRIVER_TEST_ANGLE_METHOD);
+ }
+
+ /**
* Test that the `dumpsys gpu` correctly indicates `angleInUse = 1` when ANGLE is enabled.
*/
@Test
diff --git a/hostsidetests/appcloning/Android.bp b/hostsidetests/appcloning/Android.bp
new file mode 100644
index 0000000..8b646fe
--- /dev/null
+++ b/hostsidetests/appcloning/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+ name: "CtsAppCloningHostTest",
+ srcs: [
+ "hostside/src/**/AppCloningHostTest.java",
+ "hostside/src/**/BaseHostTestCase.java",
+ ],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "testng",
+ ],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
+ test_config: "AndroidTestAppCloning.xml",
+ data: [":CtsAppCloningTestApp"],
+}
diff --git a/hostsidetests/appcloning/AndroidTestAppCloning.xml b/hostsidetests/appcloning/AndroidTestAppCloning.xml
new file mode 100644
index 0000000..8c882eb
--- /dev/null
+++ b/hostsidetests/appcloning/AndroidTestAppCloning.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="Test for App cloning support with clone user profiles">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <!-- Clone user profile is meant to exist only alongside a real system user.
+ It does not exist for a headless system user, or a secondary user -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="class" value="com.android.cts.appcloning.AppCloningHostTest" />
+ </test>
+
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
+ </object>
+</configuration>
diff --git a/hostsidetests/appcloning/OWNERS b/hostsidetests/appcloning/OWNERS
new file mode 100644
index 0000000..7cc9314
--- /dev/null
+++ b/hostsidetests/appcloning/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 1029024
+saumyap@google.com
+maco@google.com
+dagarhimanshu@google.com
+sarup@google.com
+sailendrabathi@google.com
\ No newline at end of file
diff --git a/hostsidetests/appcloning/TEST_MAPPING b/hostsidetests/appcloning/TEST_MAPPING
new file mode 100644
index 0000000..f512df9
--- /dev/null
+++ b/hostsidetests/appcloning/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsAppCloningHostTest"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java
new file mode 100644
index 0000000..ce7356c
--- /dev/null
+++ b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.appcloning;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.contentprovider.ContentProviderHandler;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.util.CommandResult;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Runs the AppCloning tests.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class AppCloningHostTest extends BaseHostTestCase {
+
+ private static final String APP_A = "CtsAppCloningTestApp.apk";
+ private static final String APP_A_PACKAGE = "com.android.cts.appcloningtestapp";
+
+ private static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
+ private static final int CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS = 20000;
+
+ private String mCloneUserId;
+ private ContentProviderHandler mContentProviderHandler;
+
+ @Before
+ public void setup() throws Exception {
+ assumeFalse("Device is in headless system user mode", isHeadlessSystemUserMode());
+ assumeTrue(isAtLeastS());
+ assumeFalse("Device uses sdcardfs", usesSdcardFs());
+
+ // create clone user
+ String output = executeShellCommand(
+ "pm create-user --profileOf 0 --user-type android.os.usertype.profile.CLONE "
+ + "testUser");
+ mCloneUserId = output.substring(output.lastIndexOf(' ') + 1).replaceAll("[^0-9]",
+ "");
+ assertThat(mCloneUserId).isNotEmpty();
+
+ CommandResult out = executeShellV2Command("am start-user -w %s", mCloneUserId);
+ assertThat(out.getStderr()).isEmpty();
+
+ mContentProviderHandler = new ContentProviderHandler(getDevice());
+ mContentProviderHandler.setUp();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (isHeadlessSystemUserMode() || !isAtLeastS() || usesSdcardFs()) return;
+ if (mContentProviderHandler != null) {
+ mContentProviderHandler.tearDown();
+ }
+
+ // remove the clone user
+ executeShellCommand("pm remove-user %s", mCloneUserId);
+ }
+
+ @Test
+ public void testInstallAppTwice() throws Exception {
+ installAppAsUser(APP_A, getCurrentUserId());
+ installAppAsUser(APP_A, Integer.valueOf(mCloneUserId));
+ uninstallPackage(APP_A_PACKAGE);
+ }
+
+ @Test
+ public void testCreateCloneUserFile() throws Exception {
+ CommandResult out;
+
+ // Check that the clone user directories exist
+ eventually(() -> {
+ // Wait for finish.
+ assertThat(isSuccessful(
+ runContentProviderCommand("query", mCloneUserId,
+ "/sdcard", ""))).isTrue();
+ }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
+
+ // Create a file on the clone user storage
+ out = executeShellV2Command("touch /sdcard/testFile.txt");
+ assertThat(isSuccessful(out)).isTrue();
+ eventually(() -> {
+ // Wait for finish.
+ assertThat(isSuccessful(
+ runContentProviderCommand("write", mCloneUserId,
+ "/sdcard/testFile.txt",
+ "< /sdcard/testFile.txt"))).isTrue();
+ }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
+
+ // Check that the above created file exists on the clone user storage
+ out = runContentProviderCommand("query", mCloneUserId,
+ "/sdcard/testFile.txt", "");
+ assertThat(isSuccessful(out)).isTrue();
+
+ // Cleanup the created file
+ out = runContentProviderCommand("delete", mCloneUserId,
+ "/sdcard/testFile.txt", "");
+ assertThat(isSuccessful(out)).isTrue();
+ }
+
+ @Test
+ public void testPrivateAppDataDirectoryForCloneUser() throws Exception {
+ installAppAsUser(APP_A, Integer.valueOf(mCloneUserId));
+ eventually(() -> {
+ // Wait for finish.
+ assertThat(isPackageInstalled(APP_A_PACKAGE, mCloneUserId)).isTrue();
+ }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
+ }
+
+ private void installAppAsUser(String packageFile, int userId)
+ throws TargetSetupError, DeviceNotAvailableException {
+ installPackageAsUser(packageFile, false, userId, "-t");
+ }
+
+ private CommandResult runContentProviderCommand(String commandType, String userId,
+ String relativePath, String args) throws Exception {
+ String fullUri = CONTENT_PROVIDER_URL + relativePath;
+ return executeShellV2Command("content %s --user %s --uri %s %s",
+ commandType, userId, fullUri, args);
+ }
+
+ private boolean usesSdcardFs() throws Exception {
+ CommandResult out = executeShellV2Command("cat /proc/mounts");
+ assertThat(isSuccessful(out)).isTrue();
+ for (String line : out.getStdout().split("\n")) {
+ String[] split = line.split(" ");
+ if (split.length >= 3 && split[2].equals("sdcardfs")) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/BaseHostTestCase.java b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/BaseHostTestCase.java
new file mode 100644
index 0000000..3d301f1
--- /dev/null
+++ b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/BaseHostTestCase.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.appcloning;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.NativeDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+
+abstract class BaseHostTestCase extends BaseHostJUnit4Test {
+ private int mCurrentUserId = NativeDevice.INVALID_USER_ID;
+ private static final String ERROR_MESSAGE_TAG = "[ERROR]";
+
+ protected String executeShellCommand(String cmd, Object... args) throws Exception {
+ return getDevice().executeShellCommand(String.format(cmd, args));
+ }
+
+ protected CommandResult executeShellV2Command(String cmd, Object... args) throws Exception {
+ return getDevice().executeShellV2Command(String.format(cmd, args));
+ }
+
+ protected boolean isPackageInstalled(String packageName, String userId) throws Exception {
+ return getDevice().isPackageInstalled(packageName, userId);
+ }
+
+ // TODO (b/174775905) remove after exposing the check from ITestDevice.
+ protected boolean isHeadlessSystemUserMode() throws DeviceNotAvailableException {
+ String result = getDevice()
+ .executeShellCommand("getprop ro.fw.mu.headless_system_user").trim();
+ return "true".equalsIgnoreCase(result);
+ }
+
+ protected boolean isAtLeastS() throws DeviceNotAvailableException {
+ return getDevice().getApiLevel() >= 31 /* BUILD.VERSION_CODES.S */;
+ }
+
+ protected static void eventually(ThrowingRunnable r, long timeoutMillis) {
+ long start = System.currentTimeMillis();
+
+ while (true) {
+ try {
+ r.run();
+ return;
+ } catch (Throwable e) {
+ if (System.currentTimeMillis() - start < timeoutMillis) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ignored) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+ protected int getCurrentUserId() throws Exception {
+ setCurrentUserId();
+
+ return mCurrentUserId;
+ }
+
+ protected boolean isSuccessful(CommandResult result) {
+ if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
+ return false;
+ }
+ String stdout = result.getStdout();
+ if (stdout.contains(ERROR_MESSAGE_TAG)) {
+ return false;
+ }
+ String stderr = result.getStderr();
+ return (stderr == null || stderr.trim().isEmpty());
+ }
+
+ private void setCurrentUserId() throws Exception {
+ if (mCurrentUserId != NativeDevice.INVALID_USER_ID) return;
+
+ ITestDevice device = getDevice();
+ mCurrentUserId = device.getCurrentUser();
+ CLog.i("Current user: %d");
+ }
+
+ protected interface ThrowingRunnable {
+ /**
+ * Similar to {@link Runnable#run} but has {@code throws Exception}.
+ */
+ void run() throws Exception;
+ }
+}
diff --git a/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp b/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp
new file mode 100644
index 0000000..b3edfec
--- /dev/null
+++ b/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp
@@ -0,0 +1,31 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAppCloningTestApp",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "androidx.test.rules",
+ "truth-prebuilt",
+ "cts-install-lib",
+ ],
+ srcs: ["src/**/*.java"],
+ sdk_version: "test_current",
+ target_sdk_version: "current",
+ min_sdk_version: "30",
+}
diff --git a/hostsidetests/appcloning/test-apps/AppCloningTestApp/AndroidManifest.xml b/hostsidetests/appcloning/test-apps/AppCloningTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..07d78a0
--- /dev/null
+++ b/hostsidetests/appcloning/test-apps/AppCloningTestApp/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.appcloningtestapp"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="30" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.appcloningtestapp" />
+
+</manifest>
\ No newline at end of file
diff --git a/hostsidetests/appcloning/test-apps/AppCloningTestApp/src/com/android/cts/appcloningtestapp/AppCloningDeviceTest.java b/hostsidetests/appcloning/test-apps/AppCloningTestApp/src/com/android/cts/appcloningtestapp/AppCloningDeviceTest.java
new file mode 100644
index 0000000..46480f7
--- /dev/null
+++ b/hostsidetests/appcloning/test-apps/AppCloningTestApp/src/com/android/cts/appcloningtestapp/AppCloningDeviceTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.appcloningtestapp;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class AppCloningDeviceTest {
+ private static final String TAG = "AppCloningDeviceTest";
+}
diff --git a/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/compatchanges/CompatChangesTest.java b/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/compatchanges/CompatChangesTest.java
index 3212c99..cd852ea 100644
--- a/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/compatchanges/CompatChangesTest.java
+++ b/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/compatchanges/CompatChangesTest.java
@@ -17,6 +17,7 @@
package com.android.cts.appcompat.compatchanges;
import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertThrows;
import android.Manifest;
@@ -34,6 +35,9 @@
import org.junit.runner.RunWith;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
/**
* Tests for the {@link android.app.compat.CompatChanges} SystemApi.
@@ -46,160 +50,199 @@
*/
@RunWith(AndroidJUnit4.class)
public final class CompatChangesTest {
- private static final long CTS_SYSTEM_API_CHANGEID = 149391281L;
- private static final long CTS_SYSTEM_API_OVERRIDABLE_CHANGEID = 174043039L;
- private static final long UNKNOWN_CHANGEID = 123L;
+ private static final long CTS_SYSTEM_API_CHANGEID = 149391281L;
+ private static final long CTS_SYSTEM_API_OVERRIDABLE_CHANGEID = 174043039L;
+ private static final long UNKNOWN_CHANGEID = 123L;
- private static final String OVERRIDE_PACKAGE = "com.android.cts.appcompat.preinstalloverride";
+ private static final String OVERRIDE_PACKAGE = "com.android.cts.appcompat.preinstalloverride";
+ private static final String OVERRIDE_PACKAGE2 = "com.android.cts.appcompat.preinstalloverride2";
- @Before
- public void setUp() {
- InstrumentationRegistry.getInstrumentation().getUiAutomation()
- .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
- Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
- Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD,
- Manifest.permission.INTERACT_ACROSS_USERS_FULL);
- }
+ @Before
+ public void setUp() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
+ Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+ Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ }
- @After
- public void tearDown() {
- InstrumentationRegistry.getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
- }
+ @After
+ public void tearDown() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
- /* Test run by CompatChangesSystemApiTest.testIsChangeEnabled */
- @Test
- public void isChangeEnabled_changeEnabled() {
- assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isTrue();
- }
+ /* Test run by CompatChangesSystemApiTest.testIsChangeEnabled */
+ @Test
+ public void isChangeEnabled_changeEnabled() {
+ assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isTrue();
+ }
- /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledPackageName */
- @Test
- public void isChangeEnabledPackageName_changeEnabled() {
- Context context = InstrumentationRegistry.getTargetContext();
- assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
- context.getUser())).isTrue();
- }
+ /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledPackageName */
+ @Test
+ public void isChangeEnabledPackageName_changeEnabled() {
+ Context context = InstrumentationRegistry.getTargetContext();
+ assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
+ context.getUser())).isTrue();
+ }
- /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledUid */
- @Test
- public void isChangeEnabledUid_changeEnabled() {
- assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isTrue();
- }
+ /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledUid */
+ @Test
+ public void isChangeEnabledUid_changeEnabled() {
+ assertThat(
+ CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isTrue();
+ }
- /* Test run by CompatChangesSystemApiTest.testIsChangeDisabled */
- @Test
- public void isChangeEnabled_changeDisabled() {
- assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isFalse();
- }
+ /* Test run by CompatChangesSystemApiTest.testIsChangeDisabled */
+ @Test
+ public void isChangeEnabled_changeDisabled() {
+ assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isFalse();
+ }
- /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledPackageName */
- @Test
- public void isChangeEnabledPackageName_changeDisabled() {
- Context context = InstrumentationRegistry.getTargetContext();
- assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
- context.getUser())).isFalse();
- }
+ /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledPackageName */
+ @Test
+ public void isChangeEnabledPackageName_changeDisabled() {
+ Context context = InstrumentationRegistry.getTargetContext();
+ assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
+ context.getUser())).isFalse();
+ }
- /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledUid */
- @Test
- public void isChangeEnabledUid_changeDisabled() {
- assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isFalse();
- }
+ /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledUid */
+ @Test
+ public void isChangeEnabledUid_changeDisabled() {
+ assertThat(
+ CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isFalse();
+ }
- @Test
- public void putPackageOverrides_securityExceptionForNonOverridableChangeId() {
- SecurityException e = assertThrows(SecurityException.class,
- () -> CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
- Collections.singletonMap(CTS_SYSTEM_API_CHANGEID,
- new PackageOverride.Builder().setEnabled(true).build())));
- assertThat(e).hasMessageThat().contains("marked as Overridable");
- }
+ @Test
+ public void putPackageOverrides_securityExceptionForNonOverridableChangeId() {
+ SecurityException e = assertThrows(SecurityException.class,
+ () -> CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+ Collections.singletonMap(CTS_SYSTEM_API_CHANGEID,
+ new PackageOverride.Builder().setEnabled(true).build())));
+ assertThat(e).hasMessageThat().contains("marked as Overridable");
+ }
- @Test
- public void putPackageOverrides_doesNothingIfChangeIsUnknown() {
- CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
- Collections.singletonMap(UNKNOWN_CHANGEID,
- new PackageOverride.Builder().setEnabled(true).build()));
- }
+ @Test
+ public void putPackageOverrides_doesNothingIfChangeIsUnknown() {
+ CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+ Collections.singletonMap(UNKNOWN_CHANGEID,
+ new PackageOverride.Builder().setEnabled(true).build()));
+ }
- @Test
- public void putPackageOverrides_success() {
- CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
- Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
- new PackageOverride.Builder().setEnabled(true).build()));
- }
+ @Test
+ public void putPackageOverrides_success() {
+ CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+ Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+ new PackageOverride.Builder().setEnabled(true).build()));
+ }
- @Test
- public void putPackageOverrides_fromVersion2() {
- CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
- Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
- new PackageOverride.Builder().setMinVersionCode(2).setEnabled(true).build()));
- }
+ @Test
+ public void putPackageOverrides_fromVersion2() {
+ CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+ Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+ new PackageOverride.Builder().setMinVersionCode(2).setEnabled(
+ true).build()));
+ }
- @Test
- public void putPackageOverrides_untilVersion1() {
- CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
- Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
- new PackageOverride.Builder().setMaxVersionCode(1).setEnabled(true).build()));
- }
+ @Test
+ public void putPackageOverrides_untilVersion1() {
+ CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+ Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+ new PackageOverride.Builder().setMaxVersionCode(1).setEnabled(
+ true).build()));
+ }
- @Test
- public void putPackageOverrides_securityExceptionForNotHoldingPermission() {
- // Adopt the normal override permission that doesn't allow to clear overrides on release builds
- InstrumentationRegistry.getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
- InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
- Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG);
+ @Test
+ public void putPackageOverrides_securityExceptionForNotHoldingPermission() {
+ // Adopt the normal override permission that doesn't allow to clear overrides on release
+ // builds
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG);
- SecurityException e = assertThrows(SecurityException.class,
- () -> CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
- Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
- new PackageOverride.Builder().setEnabled(true).build())));
- assertThat(e).hasMessageThat().contains("Cannot override compat change");
- }
+ SecurityException e = assertThrows(SecurityException.class,
+ () -> CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+ Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+ new PackageOverride.Builder().setEnabled(true).build())));
+ assertThat(e).hasMessageThat().contains("Cannot override compat change");
+ }
- @Test
- public void removePackageOverrides_securityExceptionForNonOverridableChangeId() {
- SecurityException e = assertThrows(SecurityException.class,
- () -> CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
- Collections.singleton(CTS_SYSTEM_API_CHANGEID)));
- assertThat(e).hasMessageThat().contains("marked as Overridable");
- }
+ @Test
+ public void putAllPackageOverrides_success() {
+ Map<String, Map<Long, PackageOverride>> packageNameToOverrides = new HashMap<>();
+ packageNameToOverrides.put(OVERRIDE_PACKAGE,
+ Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+ new PackageOverride.Builder().setEnabled(true).build()));
+ packageNameToOverrides.put(OVERRIDE_PACKAGE2,
+ Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+ new PackageOverride.Builder().setEnabled(true).build()));
+ CompatChanges.putAllPackageOverrides(packageNameToOverrides);
+ }
- @Test
- public void removePackageOverrides_doesNothingIfOverrideNotPresent() {
- CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
- Collections.singleton(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID));
- }
+ @Test
+ public void removePackageOverrides_securityExceptionForNonOverridableChangeId() {
+ SecurityException e = assertThrows(SecurityException.class,
+ () -> CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
+ Collections.singleton(CTS_SYSTEM_API_CHANGEID)));
+ assertThat(e).hasMessageThat().contains("marked as Overridable");
+ }
- @Test
- public void removePackageOverrides_doesNothingIfChangeIsUnknown() {
- CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
- Collections.singleton(UNKNOWN_CHANGEID));
- }
+ @Test
+ public void removePackageOverrides_doesNothingIfOverrideNotPresent() {
+ CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
+ Collections.singleton(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID));
+ }
- @Test
- public void removePackageOverrides_overridePresentSuccess() {
- CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
- Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
- new PackageOverride.Builder().setEnabled(true).build()));
- CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
- Collections.singleton(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID));
- }
+ @Test
+ public void removePackageOverrides_doesNothingIfChangeIsUnknown() {
+ CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
+ Collections.singleton(UNKNOWN_CHANGEID));
+ }
- @Test
- public void removePackageOverrides_securityExceptionForNotHoldingPermission() {
- CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
- Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
- new PackageOverride.Builder().setEnabled(true).build()));
+ @Test
+ public void removePackageOverrides_overridePresentSuccess() {
+ CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+ Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+ new PackageOverride.Builder().setEnabled(true).build()));
+ CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
+ Collections.singleton(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID));
+ }
- // Adopt the normal override permission that doesn't allow to clear overrides on release builds
- InstrumentationRegistry.getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
- InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
- Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG);
+ @Test
+ public void removePackageOverrides_securityExceptionForNotHoldingPermission() {
+ CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+ Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+ new PackageOverride.Builder().setEnabled(true).build()));
- SecurityException e = assertThrows(SecurityException.class,
- () -> CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
- Collections.singleton(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID)));
- assertThat(e).hasMessageThat().contains("Cannot override compat change");
- }
+ // Adopt the normal override permission that doesn't allow to clear overrides on release
+ // builds
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG);
+
+ SecurityException e = assertThrows(SecurityException.class,
+ () -> CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
+ Collections.singleton(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID)));
+ assertThat(e).hasMessageThat().contains("Cannot override compat change");
+ }
+
+ @Test
+ public void removeAllPackageOverrides_overridePresentSuccess() {
+ CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+ Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+ new PackageOverride.Builder().setEnabled(true).build()));
+ CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE2,
+ Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+ new PackageOverride.Builder().setEnabled(true).build()));
+
+ Map<String, Set<Long>> packageNameToOverridesToRemove = new HashMap<>();
+ packageNameToOverridesToRemove.put(OVERRIDE_PACKAGE,
+ Collections.singleton(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID));
+ packageNameToOverridesToRemove.put(OVERRIDE_PACKAGE2,
+ Collections.singleton(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID));
+ CompatChanges.removeAllPackageOverrides(packageNameToOverridesToRemove);
+ }
}
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp
index 0353cb7..9d2b53e 100644
--- a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp
@@ -79,3 +79,17 @@
"general-tests",
],
}
+
+android_test_helper_app {
+ name: "appcompat_preinstall_override2_versioncode1_release",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ static_libs: ["pre_install_override_lib"],
+ manifest: "AndroidManifest_2_versioncode1_release.xml",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+}
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_2_versioncode1_release.xml b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_2_versioncode1_release.xml
new file mode 100644
index 0000000..15a89c7
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_2_versioncode1_release.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.appcompat.preinstalloverride2"
+ android:versionCode="1">
+ <uses-sdk android:targetSdkVersion="30"/>
+ <application android:debuggable="false">
+ <activity android:name=".Empty" android:exported="true" />
+ </application>
+</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/src/com/android/cts/appcompat/preinstalloverride2/EmptyActivity.java b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/src/com/android/cts/appcompat/preinstalloverride2/EmptyActivity.java
new file mode 100644
index 0000000..fd8061f
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/src/com/android/cts/appcompat/preinstalloverride2/EmptyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.appcompat.preinstalloverride2;
+
+import android.app.Activity;
+
+public class EmptyActivity extends Activity {
+
+}
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesOverrideOnReleaseBuildTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesOverrideOnReleaseBuildTest.java
index 66fdf19..fbc7a58 100644
--- a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesOverrideOnReleaseBuildTest.java
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesOverrideOnReleaseBuildTest.java
@@ -29,6 +29,7 @@
private static final String TEST_PKG = "com.android.cts.appcompat.compatchanges";
private static final String OVERRIDE_PKG = "com.android.cts.appcompat.preinstalloverride";
+ private static final String OVERRIDE_PKG2 = "com.android.cts.appcompat.preinstalloverride2";
private static final long CTS_OVERRIDABLE_CHANGE_ID = 174043039L;
private static final long UNKNOWN_CHANGEID = 123L;
@@ -37,14 +38,17 @@
protected void setUp() throws Exception {
installPackage(TEST_APK, true);
runCommand("am compat reset-all " + OVERRIDE_PKG);
+ runCommand("am compat reset-all " + OVERRIDE_PKG2);
runCommand("settings put global force_non_debuggable_final_build_for_compat 1");
}
@Override
protected void tearDown() throws Exception {
runCommand("am compat reset-all " + OVERRIDE_PKG);
+ runCommand("am compat reset-all " + OVERRIDE_PKG2);
uninstallPackage(TEST_PKG, true);
uninstallPackage(OVERRIDE_PKG, false);
+ uninstallPackage(OVERRIDE_PKG2, false);
runCommand("settings put global force_non_debuggable_final_build_for_compat 0");
}
@@ -176,6 +180,26 @@
assertThat(ctsChange.hasOverrides).isFalse();
}
+ public void testPutAllPackageOverrides() throws Exception {
+ installPackage("appcompat_preinstall_override_versioncode1_release.apk", false);
+ installPackage("appcompat_preinstall_override2_versioncode1_release.apk", false);
+
+ runDeviceCompatTest(TEST_PKG, ".CompatChangesTest",
+ "putAllPackageOverrides_success",
+ /*enabledChanges*/ImmutableSet.of(),
+ /*disabledChanges*/ ImmutableSet.of());
+
+ Change ctsChange = getOnDeviceChangeIdConfig(CTS_OVERRIDABLE_CHANGE_ID);
+ assertWithMessage("CTS specific change %s not found on device", CTS_OVERRIDABLE_CHANGE_ID)
+ .that(ctsChange).isNotNull();
+ assertThat(ctsChange.hasRawOverrides).isTrue();
+ assertThat(ctsChange.rawOverrideStr).isEqualTo(
+ "{" + OVERRIDE_PKG + "=true, " + OVERRIDE_PKG2 + "=true}");
+ assertThat(ctsChange.hasOverrides).isTrue();
+ assertThat(ctsChange.overridesStr).isEqualTo(
+ "{" + OVERRIDE_PKG + "=true, " + OVERRIDE_PKG2 + "=true}");
+ }
+
public void testRemovePackageOverridesSecurityExceptionNonOverridableChangeId()
throws Exception {
installPackage("appcompat_preinstall_override_versioncode1_release.apk", false);
@@ -243,4 +267,20 @@
assertThat(ctsChange.hasRawOverrides).isFalse();
assertThat(ctsChange.hasOverrides).isFalse();
}
+
+ public void testRemoveAllPackageOverridesWhenOverridePresent() throws Exception {
+ installPackage("appcompat_preinstall_override_versioncode1_release.apk", false);
+ installPackage("appcompat_preinstall_override2_versioncode1_release.apk", false);
+
+ runDeviceCompatTest(TEST_PKG, ".CompatChangesTest",
+ "removeAllPackageOverrides_overridePresentSuccess",
+ /*enabledChanges*/ImmutableSet.of(),
+ /*disabledChanges*/ ImmutableSet.of());
+
+ Change ctsChange = getOnDeviceChangeIdConfig(CTS_OVERRIDABLE_CHANGE_ID);
+ assertWithMessage("CTS specific change %s not found on device", CTS_OVERRIDABLE_CHANGE_ID)
+ .that(ctsChange).isNotNull();
+ assertThat(ctsChange.hasRawOverrides).isFalse();
+ assertThat(ctsChange.hasOverrides).isFalse();
+ }
}
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java
index 4b7edc7..aa87d2a 100644
--- a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java
@@ -147,8 +147,7 @@
}
private void startApp() throws Exception {
- runCommand("am start -n " + TEST_PKG + "/" + TEST_PKG + ".Empty");
- Thread.currentThread().sleep(5000);
+ runCommand("am start-activity -W -n " + TEST_PKG + "/" + TEST_PKG + ".Empty");
}
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
index ef1c220..ad827f2 100644
--- a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
@@ -24,6 +24,10 @@
import com.google.common.collect.ImmutableSet;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -31,10 +35,6 @@
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-
public final class CompatChangesValidConfigTest extends CompatChangeGatingTestCase {
private static final Set<String> OVERRIDES_ALLOWLIST = ImmutableSet.of(
diff --git a/hostsidetests/appcompat/strictjavapackages/Android.bp b/hostsidetests/appcompat/strictjavapackages/Android.bp
index fcb20e8..3c84b1c 100644
--- a/hostsidetests/appcompat/strictjavapackages/Android.bp
+++ b/hostsidetests/appcompat/strictjavapackages/Android.bp
@@ -21,7 +21,6 @@
defaults: ["cts_defaults"],
srcs: ["src/**/*.java"],
libs: [
- "cts-tradefed",
"tradefed",
"compatibility-host-util",
],
@@ -35,4 +34,6 @@
"general-tests",
"mts",
],
+ data: [":SharedLibraryInfoTestApp"],
+ per_testcase_directory: true,
}
diff --git a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
index 04e12f9..b3e2cab 100644
--- a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
+++ b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
@@ -24,29 +24,34 @@
import static org.junit.Assume.assumeTrue;
import android.compat.testing.Classpaths;
+import android.compat.testing.SharedLibraryInfo;
-import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.modules.utils.build.testing.DeviceSdkLevel;
import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
-import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import org.jf.dexlib2.iface.ClassDef;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
import java.util.Collection;
-import java.util.HashSet;
import java.util.Set;
+import java.util.stream.Stream;
+
/**
* Tests for detecting no duplicate class files are present on BOOTCLASSPATH and
@@ -58,6 +63,16 @@
@RunWith(DeviceJUnit4ClassRunner.class)
public class StrictJavaPackagesTest extends BaseHostJUnit4Test {
+ private static final String ANDROID_TEST_MOCK_JAR = "/system/framework/android.test.mock.jar";
+
+ private static ImmutableList<String> sBootclasspathJars;
+ private static ImmutableList<String> sSystemserverclasspathJars;
+ private static ImmutableList<String> sSharedLibJars;
+ private static ImmutableList<SharedLibraryInfo> sSharedLibs;
+ private static ImmutableSetMultimap<String, String> sJarsToClasses;
+
+ private DeviceSdkLevel mDeviceSdkLevel;
+
/**
* This is the list of classes that are currently duplicated and should be addressed.
*
@@ -201,33 +216,168 @@
private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
private static final Set<String> WEAR_HIDL_OVERLAP_BURNDOWN_LIST =
- ImmutableSet.of(
- "Landroid/hidl/base/V1_0/DebugInfo$Architecture;",
- "Landroid/hidl/base/V1_0/IBase;",
- "Landroid/hidl/base/V1_0/IBase$Proxy;",
- "Landroid/hidl/base/V1_0/IBase$Stub;",
- "Landroid/hidl/base/V1_0/DebugInfo;",
- "Landroid/hidl/safe_union/V1_0/Monostate;"
- );
+ ImmutableSet.of(
+ "Landroid/hidl/base/V1_0/DebugInfo$Architecture;",
+ "Landroid/hidl/base/V1_0/IBase;",
+ "Landroid/hidl/base/V1_0/IBase$Proxy;",
+ "Landroid/hidl/base/V1_0/IBase$Stub;",
+ "Landroid/hidl/base/V1_0/DebugInfo;",
+ "Landroid/hidl/safe_union/V1_0/Monostate;"
+ );
private static final Set<String> AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST =
- ImmutableSet.of(
- "Landroid/hidl/base/V1_0/DebugInfo$Architecture;",
- "Landroid/hidl/base/V1_0/IBase;",
- "Landroid/hidl/base/V1_0/IBase$Proxy;",
- "Landroid/hidl/base/V1_0/IBase$Stub;",
- "Landroid/hidl/base/V1_0/DebugInfo;"
- );
+ ImmutableSet.of(
+ "Landroid/hidl/base/V1_0/DebugInfo$Architecture;",
+ "Landroid/hidl/base/V1_0/IBase;",
+ "Landroid/hidl/base/V1_0/IBase$Proxy;",
+ "Landroid/hidl/base/V1_0/IBase$Stub;",
+ "Landroid/hidl/base/V1_0/DebugInfo;"
+ );
+
+ /**
+ * TODO(b/199529199): Address these.
+ * List of duplicate classes between bootclasspath and shared libraries.
+ *
+ * <p> DO NOT ADD CLASSES TO THIS LIST!
+ */
+ private static final Set<String> BCP_AND_SHARED_LIB_BURNDOWN_LIST =
+ ImmutableSet.of(
+ "Landroid/hidl/base/V1_0/DebugInfo;",
+ "Landroid/hidl/base/V1_0/IBase;",
+ "Landroid/hidl/manager/V1_0/IServiceManager;",
+ "Landroid/hidl/manager/V1_0/IServiceNotification;",
+ "Landroidx/annotation/Keep;",
+ "Lcom/google/android/embms/nano/EmbmsProtos;",
+ "Lcom/google/protobuf/nano/android/ParcelableExtendableMessageNano;",
+ "Lcom/google/protobuf/nano/android/ParcelableMessageNano;",
+ "Lcom/google/protobuf/nano/android/ParcelableMessageNanoCreator;",
+ "Lcom/google/protobuf/nano/CodedInputByteBufferNano;",
+ "Lcom/google/protobuf/nano/CodedOutputByteBufferNano;",
+ "Lcom/google/protobuf/nano/ExtendableMessageNano;",
+ "Lcom/google/protobuf/nano/Extension;",
+ "Lcom/google/protobuf/nano/FieldArray;",
+ "Lcom/google/protobuf/nano/FieldData;",
+ "Lcom/google/protobuf/nano/InternalNano;",
+ "Lcom/google/protobuf/nano/InvalidProtocolBufferNanoException;",
+ "Lcom/google/protobuf/nano/MapFactories;",
+ "Lcom/google/protobuf/nano/MessageNano;",
+ "Lcom/google/protobuf/nano/MessageNanoPrinter;",
+ "Lcom/google/protobuf/nano/UnknownFieldData;",
+ "Lcom/google/protobuf/nano/WireFormatNano;",
+ "Lcom/qualcomm/qcrilhook/BaseQmiTypes;",
+ "Lcom/qualcomm/qcrilhook/CSignalStrength;",
+ "Lcom/qualcomm/qcrilhook/EmbmsOemHook;",
+ "Lcom/qualcomm/qcrilhook/EmbmsProtoUtils;",
+ "Lcom/qualcomm/qcrilhook/IOemHookCallback;",
+ "Lcom/qualcomm/qcrilhook/IQcRilHook;",
+ "Lcom/qualcomm/qcrilhook/IQcRilHookExt;",
+ "Lcom/qualcomm/qcrilhook/OemHookCallback;",
+ "Lcom/qualcomm/qcrilhook/PresenceMsgBuilder;",
+ "Lcom/qualcomm/qcrilhook/PresenceMsgParser;",
+ "Lcom/qualcomm/qcrilhook/PresenceOemHook;",
+ "Lcom/qualcomm/qcrilhook/PrimitiveParser;",
+ "Lcom/qualcomm/qcrilhook/QcRilHook;",
+ "Lcom/qualcomm/qcrilhook/QcRilHookCallback;",
+ "Lcom/qualcomm/qcrilhook/QcRilHookCallbackExt;",
+ "Lcom/qualcomm/qcrilhook/QcRilHookExt;",
+ "Lcom/qualcomm/qcrilhook/QmiOemHook;",
+ "Lcom/qualcomm/qcrilhook/QmiOemHookConstants;",
+ "Lcom/qualcomm/qcrilhook/QmiPrimitiveTypes;",
+ "Lcom/qualcomm/qcrilhook/TunerOemHook;",
+ "Lcom/qualcomm/qcrilmsgtunnel/IQcrilMsgTunnel;",
+ "Lcom/qualcomm/utils/CommandException;",
+ "Lcom/qualcomm/utils/RILConstants;",
+ "Lorg/codeaurora/telephony/utils/CommandException;",
+ "Lorg/codeaurora/telephony/utils/Log;",
+ "Lorg/codeaurora/telephony/utils/RILConstants;",
+ "Lorg/chromium/net/ApiVersion;",
+ "Lorg/chromium/net/BidirectionalStream;",
+ "Lorg/chromium/net/CallbackException;",
+ "Lorg/chromium/net/CronetEngine;",
+ "Lorg/chromium/net/CronetException;",
+ "Lorg/chromium/net/CronetProvider;",
+ "Lorg/chromium/net/EffectiveConnectionType;",
+ "Lorg/chromium/net/ExperimentalBidirectionalStream;",
+ "Lorg/chromium/net/ExperimentalCronetEngine;",
+ "Lorg/chromium/net/ExperimentalUrlRequest;",
+ "Lorg/chromium/net/ICronetEngineBuilder;",
+ "Lorg/chromium/net/InlineExecutionProhibitedException;",
+ "Lorg/chromium/net/NetworkException;",
+ "Lorg/chromium/net/NetworkQualityRttListener;",
+ "Lorg/chromium/net/NetworkQualityThroughputListener;",
+ "Lorg/chromium/net/QuicException;",
+ "Lorg/chromium/net/RequestFinishedInfo;",
+ "Lorg/chromium/net/RttThroughputValues;",
+ "Lorg/chromium/net/ThreadStatsUid;",
+ "Lorg/chromium/net/UploadDataProvider;",
+ "Lorg/chromium/net/UploadDataProviders;",
+ "Lorg/chromium/net/UploadDataSink;",
+ "Lorg/chromium/net/UrlRequest;",
+ "Lorg/chromium/net/UrlResponseInfo;"
+ );
+
+ /**
+ * Fetch all jar files in BCP, SSCP and shared libs and extract all the classes.
+ *
+ * <p>This method cannot be static, as there are no static equivalents for {@link #getDevice()}
+ * and {@link #getBuild()}.
+ */
+ @BeforeClassWithInfo
+ public static void setupOnce(TestInformation testInfo) throws Exception {
+ if (testInfo.getDevice() == null || testInfo.getBuildInfo() == null) {
+ throw new RuntimeException("No device and/or build type specified!");
+ }
+ DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(testInfo.getDevice());
+
+ sBootclasspathJars = Classpaths.getJarsOnClasspath(testInfo.getDevice(), BOOTCLASSPATH);
+ sSystemserverclasspathJars =
+ Classpaths.getJarsOnClasspath(testInfo.getDevice(), SYSTEMSERVERCLASSPATH);
+ sSharedLibs = deviceSdkLevel.isDeviceAtLeastS()
+ ? Classpaths.getSharedLibraryInfos(testInfo.getDevice(), testInfo.getBuildInfo())
+ : ImmutableList.of();
+ sSharedLibJars = sSharedLibs.stream()
+ .map(sharedLibraryInfo -> sharedLibraryInfo.paths)
+ .flatMap(ImmutableCollection::stream)
+ .filter(file -> doesFileExist(file, testInfo.getDevice()))
+ .collect(ImmutableList.toImmutableList());
+
+ final ImmutableSetMultimap.Builder<String, String> jarsToClasses =
+ ImmutableSetMultimap.builder();
+ Stream.of(sBootclasspathJars.stream(),
+ sSystemserverclasspathJars.stream(),
+ sSharedLibJars.stream())
+ .reduce(Stream::concat).orElseGet(Stream::empty)
+ .parallel()
+ .forEach(jar -> {
+ try {
+ ImmutableSet<String> classes =
+ Classpaths.getClassDefsFromJar(testInfo.getDevice(), jar).stream()
+ .map(ClassDef::getType)
+ // Inner classes always go with their parent.
+ .filter(className -> !className.contains("$"))
+ .collect(ImmutableSet.toImmutableSet());
+ synchronized (jarsToClasses) {
+ jarsToClasses.putAll(jar, classes);
+ }
+ } catch (DeviceNotAvailableException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ sJarsToClasses = jarsToClasses.build();
+ }
+
+ @Before
+ public void setup() {
+ mDeviceSdkLevel = new DeviceSdkLevel(getDevice());
+ }
/**
* Ensure that there are no duplicate classes among jars listed in BOOTCLASSPATH.
*/
@Test
public void testBootclasspath_nonDuplicateClasses() throws Exception {
- assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29));
- ImmutableList<String> jars =
- Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH);
- assertThat(getDuplicateClasses(jars)).isEmpty();
+ assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR());
+ assertThat(getDuplicateClasses(sBootclasspathJars)).isEmpty();
}
/**
@@ -235,9 +385,7 @@
*/
@Test
public void testSystemServerClasspath_nonDuplicateClasses() throws Exception {
- assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29));
- ImmutableList<String> jars =
- Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH);
+ assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR());
ImmutableSet<String> overlapBurndownList;
if (hasFeature(FEATURE_AUTOMOTIVE)) {
overlapBurndownList = ImmutableSet.copyOf(AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST);
@@ -246,7 +394,7 @@
} else {
overlapBurndownList = ImmutableSet.of();
}
- Multimap<String, String> duplicates = getDuplicateClasses(jars);
+ Multimap<String, String> duplicates = getDuplicateClasses(sSystemserverclasspathJars);
Multimap<String, String> filtered = Multimaps.filterKeys(duplicates,
duplicate -> !overlapBurndownList.contains(duplicate));
@@ -259,10 +407,10 @@
*/
@Test
public void testBootClasspathAndSystemServerClasspath_nonDuplicateClasses() throws Exception {
- assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29));
+ assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR());
ImmutableList.Builder<String> jars = ImmutableList.builder();
- jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH));
- jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH));
+ jars.addAll(sBootclasspathJars);
+ jars.addAll(sSystemserverclasspathJars);
ImmutableSet<String> overlapBurndownList;
if (hasFeature(FEATURE_AUTOMOTIVE)) {
overlapBurndownList = ImmutableSet.<String>builder()
@@ -287,13 +435,9 @@
*/
@Test
public void testBootClasspath_nonDuplicateApexJarClasses() throws Exception {
- ImmutableList<String> jars =
- Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH);
-
- Multimap<String, String> duplicates = getDuplicateClasses(jars);
+ Multimap<String, String> duplicates = getDuplicateClasses(sBootclasspathJars);
Multimap<String, String> filtered =
Multimaps.filterValues(duplicates, jar -> jar.startsWith("/apex/"));
-
assertThat(filtered).isEmpty();
}
@@ -302,10 +446,7 @@
*/
@Test
public void testSystemServerClasspath_nonDuplicateApexJarClasses() throws Exception {
- ImmutableList<String> jars =
- Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH);
-
- Multimap<String, String> duplicates = getDuplicateClasses(jars);
+ Multimap<String, String> duplicates = getDuplicateClasses(sSystemserverclasspathJars);
Multimap<String, String> filtered =
Multimaps.filterValues(duplicates, jar -> jar.startsWith("/apex/"));
@@ -320,8 +461,8 @@
public void testBootClasspathAndSystemServerClasspath_nonApexDuplicateClasses()
throws Exception {
ImmutableList.Builder<String> jars = ImmutableList.builder();
- jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH));
- jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH));
+ jars.addAll(sBootclasspathJars);
+ jars.addAll(sSystemserverclasspathJars);
Multimap<String, String> duplicates = getDuplicateClasses(jars.build());
Multimap<String, String> filtered = Multimaps.filterKeys(duplicates,
@@ -332,34 +473,89 @@
}
/**
+ * Ensure that there are no duplicate classes among jars listed in BOOTCLASSPATH and
+ * shared library jars.
+ */
+ @Test
+ public void testBootClasspathAndSharedLibs_nonDuplicateClasses() throws Exception {
+ assumeTrue(mDeviceSdkLevel.isDeviceAtLeastS());
+ final ImmutableList.Builder<String> jars = ImmutableList.builder();
+ jars.addAll(sBootclasspathJars);
+ jars.addAll(sSharedLibJars);
+ final Multimap<String, String> duplicates = getDuplicateClasses(jars.build());
+ final Multimap<String, String> filtered = Multimaps.filterKeys(duplicates,
+ dupeClass -> {
+ try {
+ final Collection<String> dupeJars = duplicates.get(dupeClass);
+ // Duplicate is already known.
+ if (BCP_AND_SHARED_LIB_BURNDOWN_LIST.contains(dupeClass)) {
+ return false;
+ }
+ // Duplicate is only between different versions of the same shared library.
+ if (isSameLibrary(dupeJars)) {
+ return false;
+ }
+ // Pre-T, the Android test mock library included some platform classes.
+ if (!mDeviceSdkLevel.isDeviceAtLeastT()
+ && dupeJars.contains(ANDROID_TEST_MOCK_JAR)) {
+ return false;
+ }
+ // Different versions of the same library may have different names, and
+ // there's
+ // no reliable way to dedupe them. Ignore duplicates if they do not
+ // include apex jars.
+ if (dupeJars.stream().noneMatch(lib -> lib.startsWith("/apex/"))) {
+ return false;
+ }
+ } catch (DeviceNotAvailableException e) {
+ throw new RuntimeException(e);
+ }
+ return true;
+ });
+ assertThat(filtered).isEmpty();
+ }
+
+ /**
* Gets the duplicate classes within a list of jar files.
*
* @param jars a list of jar files.
* @return a multimap with the class name as a key and the jar files as a value.
*/
- private Multimap<String, String> getDuplicateClasses(ImmutableCollection<String> jars)
- throws Exception {
- final Multimap<String, String> allClasses = HashMultimap.create();
- for (String jar : jars) {
- ImmutableSet<ClassDef> classes = Classpaths.getClassDefsFromJar(getDevice(), jar);
- for (ClassDef classDef : classes) {
- // No need to worry about inner classes, as they always go with their parent.
- if (!classDef.getType().contains("$")) {
- allClasses.put(classDef.getType(), jar);
- }
- }
- }
+ private Multimap<String, String> getDuplicateClasses(ImmutableCollection<String> jars) {
+ final HashMultimap<String, String> allClasses = HashMultimap.create();
+ Multimaps.invertFrom(Multimaps.filterKeys(sJarsToClasses, jars::contains), allClasses);
+ return Multimaps.filterKeys(allClasses, key -> allClasses.get(key).size() > 1);
+ }
- final Multimap<String, String> duplicates = HashMultimap.create();
- for (String clazz : allClasses.keySet()) {
- Collection<String> jarsWithClazz = allClasses.get(clazz);
- if (jarsWithClazz.size() > 1) {
- CLog.i("Class %s is duplicated in %s", clazz, jarsWithClazz);
- duplicates.putAll(clazz, jarsWithClazz);
- }
+ private static boolean doesFileExist(String path, ITestDevice device) {
+ assertThat(path).isNotNull();
+ try {
+ return device.doesFileExist(path);
+ } catch (DeviceNotAvailableException e) {
+ throw new RuntimeException("Could not check whether " + path + " exists on device", e);
}
+ }
- return duplicates;
+ /**
+ * Get the name of a shared library.
+ *
+ * @return the shared library name or the jar's path if it's not a shared library.
+ */
+ private String getSharedLibraryNameOrPath(String jar) {
+ return sSharedLibs.stream()
+ .filter(sharedLib -> sharedLib.paths.contains(jar))
+ .map(sharedLib -> sharedLib.name)
+ .findFirst().orElse(jar);
+ }
+
+ /**
+ * Check whether a list of jars are all different versions of the same library.
+ */
+ private boolean isSameLibrary(Collection<String> jars) {
+ return jars.stream()
+ .map(this::getSharedLibraryNameOrPath)
+ .distinct()
+ .count() == 1;
}
private boolean hasFeature(String featureName) throws DeviceNotAvailableException {
diff --git a/hostsidetests/appsearch/Android.bp b/hostsidetests/appsearch/Android.bp
index 57a33b8..36a27d7 100644
--- a/hostsidetests/appsearch/Android.bp
+++ b/hostsidetests/appsearch/Android.bp
@@ -29,11 +29,12 @@
"tools-common-prebuilt",
"cts-tradefed",
"tradefed",
- "truth-prebuilt"
+ "truth-prebuilt",
],
test_suites: [
"cts",
"general-tests",
+ "mts-appsearch",
],
}
@@ -53,6 +54,7 @@
test_suites: [
"cts",
"general-tests",
+ "mts-appsearch",
],
manifest: "test-apps/AppSearchHostTestHelperA/AndroidManifest.xml",
certificate: ":cts-appsearch-hosttest-helper-cert-a",
@@ -75,6 +77,7 @@
test_suites: [
"cts",
"general-tests",
+ "mts-appsearch",
],
manifest: "test-apps/AppSearchHostTestHelperB/AndroidManifest.xml",
certificate: ":cts-appsearch-hosttest-helper-cert-b",
diff --git a/hostsidetests/appsearch/AndroidTest.xml b/hostsidetests/appsearch/AndroidTest.xml
index 229e352..a538916 100644
--- a/hostsidetests/appsearch/AndroidTest.xml
+++ b/hostsidetests/appsearch/AndroidTest.xml
@@ -28,4 +28,8 @@
<option name="test-file-name" value="CtsAppSearchHostTestHelperB.apk" />
<option name="cleanup-apks" value="true" />
</target_preparer>
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.appsearch" />
+ </object>
</configuration>
diff --git a/hostsidetests/appsearch/src/android/appsearch/cts/AppSearchMultiUserTest.java b/hostsidetests/appsearch/src/android/appsearch/cts/AppSearchMultiUserTest.java
index 33ccf39..eb0ef3c 100644
--- a/hostsidetests/appsearch/src/android/appsearch/cts/AppSearchMultiUserTest.java
+++ b/hostsidetests/appsearch/src/android/appsearch/cts/AppSearchMultiUserTest.java
@@ -20,9 +20,11 @@
import static org.junit.Assume.assumeTrue;
+import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,79 +47,100 @@
@RunWith(DeviceJUnit4ClassRunner.class)
public class AppSearchMultiUserTest extends AppSearchHostTestBase {
- private int mInitialUserId;
- private int mSecondaryUserId;
+ private static int sInitialUserId;
+ private static int sSecondaryUserId;
+
+ @BeforeClassWithInfo
+ public static void setUpClass(TestInformation testInfo) throws Exception {
+ assumeTrue("Multi-user is not supported on this device",
+ testInfo.getDevice().isMultiUserSupported());
+
+ sInitialUserId = testInfo.getDevice().getPrimaryUserId();
+ sSecondaryUserId = testInfo.getDevice().createUser("Test_User");
+ assertThat(testInfo.getDevice().startUser(sSecondaryUserId)).isTrue();
+ }
@Before
public void setUp() throws Exception {
- assumeTrue("Multi-user is not supported on this device",
- getDevice().isMultiUserSupported());
+ if (!getDevice().isUserRunning(sSecondaryUserId)) {
+ getDevice().startUser(sSecondaryUserId, /*waitFlag=*/true);
+ }
+ installPackageAsUser(TARGET_APK_A, /* grantPermission= */true, sInitialUserId);
+ installPackageAsUser(TARGET_APK_A, /* grantPermission= */true, sSecondaryUserId);
- mInitialUserId = getDevice().getCurrentUser();
- mSecondaryUserId = getDevice().createUser("Test_User");
- assertThat(getDevice().startUser(mSecondaryUserId)).isTrue();
-
- installPackageAsUser(TARGET_APK_A, /* grantPermission= */true, mInitialUserId);
- installPackageAsUser(TARGET_APK_A, /* grantPermission= */true, mSecondaryUserId);
-
- runDeviceTestAsUserInPkgA("clearTestData", mInitialUserId);
- runDeviceTestAsUserInPkgA("clearTestData", mSecondaryUserId);
+ runDeviceTestAsUserInPkgA("clearTestData", sInitialUserId);
+ runDeviceTestAsUserInPkgA("clearTestData", sSecondaryUserId);
}
- @After
- public void tearDown() throws Exception {
- if (getDevice().getInstalledPackageNames().contains(TARGET_PKG_A)) {
- runDeviceTestAsUserInPkgA("clearTestData", mInitialUserId);
- }
- if (mSecondaryUserId > 0) {
- getDevice().removeUser(mSecondaryUserId);
+ @AfterClassWithInfo
+ public static void tearDownClass(TestInformation testInfo) throws Exception {
+ if (sSecondaryUserId > 0) {
+ testInfo.getDevice().removeUser(sSecondaryUserId);
}
}
@Test
- public void testMultiUser_documentAccess() throws Exception {
- runDeviceTestAsUserInPkgA("testPutDocuments", mSecondaryUserId);
- runDeviceTestAsUserInPkgA("testGetDocuments_exist", mSecondaryUserId);
+ public void testMultiUser_cantAccessOtherUsersData() throws Exception {
+ runDeviceTestAsUserInPkgA("testPutDocuments", sSecondaryUserId);
+ runDeviceTestAsUserInPkgA("testGetDocuments_exist", sSecondaryUserId);
// Cannot get the document from another user.
- runDeviceTestAsUserInPkgA("testGetDocuments_nonexist", mInitialUserId);
+ runDeviceTestAsUserInPkgA("testGetDocuments_nonexist", sInitialUserId);
+ }
+
+ @Test
+ public void testMultiUser_canInteractAsAnotherUser() throws Exception {
+ Map<String, String> args =
+ Collections.singletonMap(USER_ID_KEY, String.valueOf(sSecondaryUserId));
+
+ // We can do the normal set of operations while pretending to be another user.
+ runDeviceTestAsUserInPkgA("testPutDocumentsAsAnotherUser", sInitialUserId, args);
+ runDeviceTestAsUserInPkgA("testGetDocumentsAsAnotherUser_exist", sInitialUserId, args);
+ }
+
+ @Test
+ public void testCreateSessionInStoppedUser() throws Exception {
+ Map<String, String> args =
+ Collections.singletonMap(USER_ID_KEY, String.valueOf(sSecondaryUserId));
+ getDevice().stopUser(sSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
+ runDeviceTestAsUserInPkgA("createSessionInStoppedUser", sInitialUserId, args);
}
@Test
public void testStopUser_persistData() throws Exception {
- runDeviceTestAsUserInPkgA("testPutDocuments", mSecondaryUserId);
- runDeviceTestAsUserInPkgA("testGetDocuments_exist", mSecondaryUserId);
- getDevice().stopUser(mSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
- getDevice().startUser(mSecondaryUserId, /*waitFlag=*/true);
- runDeviceTestAsUserInPkgA("testGetDocuments_exist", mSecondaryUserId);
+ runDeviceTestAsUserInPkgA("testPutDocuments", sSecondaryUserId);
+ runDeviceTestAsUserInPkgA("testGetDocuments_exist", sSecondaryUserId);
+ getDevice().stopUser(sSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
+ getDevice().startUser(sSecondaryUserId, /*waitFlag=*/true);
+ runDeviceTestAsUserInPkgA("testGetDocuments_exist", sSecondaryUserId);
}
@Test
public void testPackageUninstall_onLockedUser() throws Exception {
- installPackageAsUser(TARGET_APK_B, /* grantPermission= */true, mSecondaryUserId);
+ installPackageAsUser(TARGET_APK_B, /* grantPermission= */true, sSecondaryUserId);
// package A grants visibility to package B.
- runDeviceTestAsUserInPkgA("testPutDocuments", mSecondaryUserId);
+ runDeviceTestAsUserInPkgA("testPutDocuments", sSecondaryUserId);
// query the document from another package.
- runDeviceTestAsUserInPkgB("testGlobalGetDocuments_exist", mSecondaryUserId);
- getDevice().stopUser(mSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
+ runDeviceTestAsUserInPkgB("testGlobalGetDocuments_exist", sSecondaryUserId);
+ getDevice().stopUser(sSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
uninstallPackage(TARGET_PKG_A);
- getDevice().startUser(mSecondaryUserId, /*waitFlag=*/true);
- runDeviceTestAsUserInPkgB("testGlobalGetDocuments_nonexist", mSecondaryUserId);
+ getDevice().startUser(sSecondaryUserId, /*waitFlag=*/true);
+ runDeviceTestAsUserInPkgB("testGlobalGetDocuments_nonexist", sSecondaryUserId);
}
@Test
public void testReadStorageInfoFromFile() throws Exception {
- runDeviceTestAsUserInPkgA("testPutDocuments", mSecondaryUserId);
+ runDeviceTestAsUserInPkgA("testPutDocuments", sSecondaryUserId);
- getDevice().stopUser(mSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
- getDevice().startUser(mSecondaryUserId, /*waitFlag=*/true);
+ getDevice().stopUser(sSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
+ getDevice().startUser(sSecondaryUserId, /*waitFlag=*/true);
// Write user's storage info into file while initializing AppSearchImpl.
- runStorageAugmenterDeviceTestAsUserInPkgA("connectToAppSearch", mSecondaryUserId);
+ runStorageAugmenterDeviceTestAsUserInPkgA("connectToAppSearch", sSecondaryUserId);
// Disconnect user from AppSearch. While AppSearchImpl doesn't exist for the user, user's
// storage info would be read from file.
- getDevice().stopUser(mSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
- getDevice().startUser(mSecondaryUserId, /*waitFlag=*/true);
+ getDevice().stopUser(sSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
+ getDevice().startUser(sSecondaryUserId, /*waitFlag=*/true);
- runStorageAugmenterDeviceTestAsUserInPkgA("testReadStorageInfo", mSecondaryUserId);
+ runStorageAugmenterDeviceTestAsUserInPkgA("testReadStorageInfo", sSecondaryUserId);
}
}
diff --git a/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchDeviceTest.java b/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchDeviceTest.java
index b1f2293..b5ec0396 100644
--- a/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchDeviceTest.java
+++ b/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchDeviceTest.java
@@ -16,13 +16,14 @@
package android.appsearch.app.a;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.doGet;
+import static android.app.appsearch.testutil.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static android.app.appsearch.testutil.AppSearchTestUtils.doGet;
import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.expectThrows;
+import android.Manifest;
import android.app.UiAutomation;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
@@ -34,13 +35,12 @@
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.PutDocumentsRequest;
import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
import android.os.Bundle;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-
import com.google.common.io.BaseEncoding;
import org.junit.Before;
@@ -109,12 +109,60 @@
}
@Test
+ public void testPutDocumentsAsAnotherUser() throws Exception {
+ mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ try {
+ Bundle args = InstrumentationRegistry.getArguments();
+ int userId = Integer.parseInt(args.getString(USER_ID_KEY));
+
+ // Initialize as other user
+ AppSearchSessionShim db = AppSearchSessionShimImpl.createSearchSession(
+ new AppSearchManager.SearchContext.Builder(DB_NAME).build(),
+ userId).get();
+
+ // Schema registration
+ db.setSchema(new SetSchemaRequest.Builder().addSchemas(SCHEMA)
+ .setSchemaTypeVisibilityForPackage(SCHEMA.getSchemaType(), /*visible=*/ true,
+ new PackageIdentifier(PKG_B, PKG_B_CERT_SHA256)).build()).get();
+
+ // Index a document
+ AppSearchBatchResult<String, Void> result = checkIsBatchResultSuccess(
+ db.put(new PutDocumentsRequest.Builder().addGenericDocuments(DOCUMENT).build()));
+ assertThat(result.getSuccesses()).containsExactly(ID, /*v0=*/null);
+ assertThat(result.getFailures()).isEmpty();
+
+ } finally {
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
public void testGetDocuments_exist() throws Exception {
List<GenericDocument> outDocuments = doGet(mDb, NAMESPACE, ID);
assertThat(outDocuments).containsExactly(DOCUMENT);
}
@Test
+ public void testGetDocumentsAsAnotherUser_exist() throws Exception {
+ mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ try {
+ Bundle args = InstrumentationRegistry.getArguments();
+ int userId = Integer.parseInt(args.getString(USER_ID_KEY));
+
+ // Initialize as other user
+ AppSearchSessionShim db = AppSearchSessionShimImpl.createSearchSession(
+ new AppSearchManager.SearchContext.Builder(DB_NAME).build(),
+ userId).get();
+
+ // Get documents
+ List<GenericDocument> outDocuments = doGet(db, NAMESPACE, ID);
+ assertThat(outDocuments).containsExactly(DOCUMENT);
+ } finally {
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
public void closeAndFlush() {
mDb.close();
}
@@ -137,4 +185,20 @@
public void clearTestData() throws Exception {
mDb.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
}
+
+ @Test
+ public void createSessionInStoppedUser() {
+ mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ try {
+ Bundle args = InstrumentationRegistry.getArguments();
+ int userId = Integer.parseInt(args.getString(USER_ID_KEY));
+ ExecutionException exception = expectThrows(ExecutionException.class, () ->
+ AppSearchSessionShimImpl.createSearchSession(
+ new AppSearchManager.SearchContext.Builder(DB_NAME).build(),
+ userId).get());
+ assertThat(exception.getMessage()).contains("is locked or not running.");
+ } finally {
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+ }
}
diff --git a/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchInstantAppTest.java b/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchInstantAppTest.java
index d397fc5..1fe992a 100644
--- a/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchInstantAppTest.java
+++ b/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchInstantAppTest.java
@@ -21,13 +21,12 @@
import static org.testng.Assert.expectThrows;
import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchStorageAugmenterDeviceTest.java b/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchStorageAugmenterDeviceTest.java
index 3e91498..6e87b19 100644
--- a/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchStorageAugmenterDeviceTest.java
+++ b/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchStorageAugmenterDeviceTest.java
@@ -21,6 +21,7 @@
import static com.google.common.truth.Truth.assertThat;
import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
import android.app.usage.StorageStats;
import android.app.usage.StorageStatsManager;
import android.content.Context;
@@ -29,8 +30,6 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperB/src/android/appsearch/app/b/AppSearchDeviceTest.java b/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperB/src/android/appsearch/app/b/AppSearchDeviceTest.java
index fa9132d..1cbb442 100644
--- a/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperB/src/android/appsearch/app/b/AppSearchDeviceTest.java
+++ b/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperB/src/android/appsearch/app/b/AppSearchDeviceTest.java
@@ -16,18 +16,17 @@
package android.appsearch.app.b;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
+import static android.app.appsearch.testutil.AppSearchTestUtils.convertSearchResultsToDocuments;
import static com.google.common.truth.Truth.assertThat;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.GlobalSearchSessionShim;
import android.app.appsearch.SearchSpec;
+import android.app.appsearch.testutil.GlobalSearchSessionShimImpl;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.android.server.appsearch.testing.GlobalSearchSessionShimImpl;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/hostsidetests/appsecurity/Android.bp b/hostsidetests/appsecurity/Android.bp
index af53c8e..9a06caa 100644
--- a/hostsidetests/appsecurity/Android.bp
+++ b/hostsidetests/appsecurity/Android.bp
@@ -73,8 +73,3 @@
name: "CtsHostsideTestsAppSecurityUtil",
srcs: ["src/android/appsecurity/cts/Utils.java"],
}
-
-filegroup {
- name: "cts-ec-p256-por_1_2-default-caps.lineage",
- srcs: ["certs/pkgsigverify/ec-p256-por_1_2-default-caps"],
-}
diff --git a/hostsidetests/appsecurity/OWNERS b/hostsidetests/appsecurity/OWNERS
index e4d9d04..056dc98 100644
--- a/hostsidetests/appsecurity/OWNERS
+++ b/hostsidetests/appsecurity/OWNERS
@@ -9,7 +9,7 @@
per-file AppSecurityTests.java = cbrubaker@google.com
per-file AuthBoundKeyTest.java = file:test-apps/AuthBoundKeyApp/OWNERS
per-file BaseInstallMultiple.java = toddke@google.com,patb@google.com
-per-file CorruptApkTests.java = rtmitchell@google.com
+per-file CorruptApkTests.java = zyy@google.com
per-file DeviceIdentifierTest.java = cbrubaker@google.com
per-file EphemeralTest.java = toddke@google.com,patb@google.com
per-file ExternalStorageHostTest.java = nandana@google.com
@@ -20,7 +20,7 @@
per-file KeySetHostTest.java = cbrubaker@google.com
per-file ListeningPortsTest.java = cbrubaker@google.com
per-file MajorVersionTest.java = toddke@google.com,patb@google.com
-per-file OverlayHostTest.java = rtmitchell@google.com
+per-file OverlayHostTest.java = zyy@google.com
per-file Package* = chiuwinson@google.com,patb@google.com,toddke@google.com
per-file PermissionsHostTest.java = moltmann@google.com
per-file Pkg* = chiuwinson@google.com,patb@google.com,toddke@google.com
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryServiceTest_v2-tgt-33.apk b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryServiceTest_v2-tgt-33.apk
new file mode 100644
index 0000000..869f9a9
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryServiceTest_v2-tgt-33.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryServiceTest_v2-tgt-33.apk.idsig b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryServiceTest_v2-tgt-33.apk.idsig
new file mode 100644
index 0000000..fb9b02f
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryServiceTest_v2-tgt-33.apk.idsig
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-legacyV4.apk b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-legacyV4.apk
new file mode 100644
index 0000000..d7a03f1
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-legacyV4.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-legacyV4.apk.idsig b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-legacyV4.apk.idsig
new file mode 100644
index 0000000..1e86976
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-legacyV4.apk.idsig
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongDigest.apk b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongDigest.apk
new file mode 100644
index 0000000..1139176
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongDigest.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongDigest.apk.idsig b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongDigest.apk.idsig
new file mode 100644
index 0000000..1f137ba
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongDigest.apk.idsig
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongV41Block.apk b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongV41Block.apk
new file mode 100644
index 0000000..38ab895
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongV41Block.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongV41Block.apk.idsig b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongV41Block.apk.idsig
new file mode 100644
index 0000000..2773f50
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongV41Block.apk.idsig
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33.apk b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33.apk
new file mode 100644
index 0000000..69a5170
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33.apk.idsig b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33.apk.idsig
new file mode 100644
index 0000000..51738c7
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33.apk.idsig
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v3-tgt-33.apk b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v3-tgt-33.apk
new file mode 100644
index 0000000..2f03585
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v3-tgt-33.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v3-tgt-33.apk.idsig b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v3-tgt-33.apk.idsig
new file mode 100644
index 0000000..0581b7a
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v3-tgt-33.apk.idsig
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/generate-apks.sh b/hostsidetests/appsecurity/res/pkgsigverify/generate-apks.sh
index 0adf311..2f4c5f8 100644
--- a/hostsidetests/appsecurity/res/pkgsigverify/generate-apks.sh
+++ b/hostsidetests/appsecurity/res/pkgsigverify/generate-apks.sh
@@ -39,4 +39,9 @@
apksigner sign --v2-signing-enabled true --v3-signing-enabled false --v4-signing-enabled --key cts/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256.pk8 --cert cts/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256.x509.pem -out cts/hostsidetests/appsecurity/res/pkgsigverify/v4-inc-to-v2-noninc-ec-p256-appv2.apk cts/hostsidetests/appsecurity/res/pkgsigverify/originalv2.apk
# testInstallV4With* tests
-apksigner sign --v2-signing-enabled false --v3-signing-enabled true --v4-signing-enabled --key vendor/google/certs/devkeys/devkey.pk8 --cert vendor/google/certs/devkeys/devkey.x509.pem -out cts/hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-128bytes-additional-data.apk cts/hostsidetests/appsecurity/res/pkgsigverify/original.apk
\ No newline at end of file
+apksigner sign --v2-signing-enabled false --v3-signing-enabled true --v4-signing-enabled --key vendor/google/certs/devkeys/devkey.pk8 --cert vendor/google/certs/devkeys/devkey.x509.pem -out cts/hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-128bytes-additional-data.apk cts/hostsidetests/appsecurity/res/pkgsigverify/original.apk
+
+# testInstallV41* tests
+apksigner sign --in CtsSignatureQueryService_v2-tgt-33.apk --cert ../../certs/pkgsigverify/ec-p256.x509.pem --key ../../certs/pkgsigverify/ec-p256.pk8 --next-signer --cert ../../certs/pkgsigverify/ec-p256_2.x509.pem --key ../../certs/pkgsigverify/ec-p256_2.pk8 --lineage ../../certs/pkgsigverify/ec-p256-por_1_2-default-caps --rotation-min-sdk-version 33 --v4-signing-enabled
+apksigner sign --in CtsSignatureQueryService_v3-tgt-33.apk --cert ../../certs/pkgsigverify/ec-p256.x509.pem --key ../../certs/pkgsigverify/ec-p256.pk8 --next-signer --cert ../../certs/pkgsigverify/ec-p256_2.x509.pem --key ../../certs/pkgsigverify/ec-p256_2.pk8 --lineage ../../certs/pkgsigverify/ec-p256-por_1_2-default-caps --rotation-min-sdk-version 33 --v4-signing-enabled
+apksigner sign --in CtsSignatureQueryServiceTest_v2-tgt-33.apk --cert ../../certs/pkgsigverify/ec-p256.x509.pem --key ../../certs/pkgsigverify/ec-p256.pk8 --next-signer --cert ../../certs/pkgsigverify/ec-p256_2.x509.pem --key ../../certs/pkgsigverify/ec-p256_2.pk8 --lineage ../../certs/pkgsigverify/ec-p256-por_1_2-default-caps --rotation-min-sdk-version 33 --v4-signing-enabled
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v31-block-stripped-v3-attr-value-33.apk b/hostsidetests/appsecurity/res/pkgsigverify/v31-block-stripped-v3-attr-value-33.apk
new file mode 100644
index 0000000..3ca0c30
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v31-block-stripped-v3-attr-value-33.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-100001.apk b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-100001.apk
new file mode 100644
index 0000000..20a2b0ab
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-100001.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-31-dev-release.apk b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-31-dev-release.apk
new file mode 100644
index 0000000..7129020
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-31-dev-release.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27-and-100001.apk b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27-and-100001.apk
new file mode 100644
index 0000000..8a6a52c
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27-and-100001.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27.apk b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27.apk
new file mode 100644
index 0000000..0ffe9f1
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-100001.apk b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-100001.apk
new file mode 100644
index 0000000..c7bed06
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-100001.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-modified.apk b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-modified.apk
new file mode 100644
index 0000000..89475b8
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-modified.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33.apk b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33.apk
new file mode 100644
index 0000000..c20792a
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
index 620c9eb..96e36cf 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
@@ -16,13 +16,14 @@
package android.appsecurity.cts;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
import static com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel;
import static com.android.compatibility.common.util.PropertyUtil.getVendorApiLevel;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
import com.android.compatibility.common.util.CddTest;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -39,6 +40,7 @@
import junitparams.Parameters;
+@Presubmit
@RunWith(DeviceParameterizedRunner.class)
@AppModeFull
public final class ApkVerityInstallTest extends BaseAppSecurityTest {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
index 98150c4..a0137dc 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
@@ -23,8 +23,9 @@
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.AppModeInstant;
-import android.platform.test.annotations.RestrictedBuildTest;
import android.platform.test.annotations.AsbSecurityTest;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RestrictedBuildTest;
import com.android.ddmlib.Log;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -41,6 +42,7 @@
* Set of tests that verify various security checks involving multiple apps are
* properly enforced.
*/
+@Presubmit
@RunWith(DeviceJUnit4ClassRunner.class)
public class AppSecurityTests extends BaseAppSecurityTest {
@@ -85,6 +87,15 @@
private static final String DUPLICATE_DECLARE_PERMISSION_PKG =
"com.android.cts.duplicatepermissiondeclareapp";
+ private static final String DUPLICATE_PERMISSION_DIFFERENT_PROTECTION_LEVEL_APK =
+ "CtsDuplicatePermissionDeclareApp_DifferentProtectionLevel.apk";
+ private static final String DUPLICATE_PERMISSION_DIFFERENT_PROTECTION_LEVEL_PKG =
+ "com.android.cts.duplicatepermission.differentprotectionlevel";
+ private static final String DUPLICATE_PERMISSION_SAME_PROTECTION_LEVEL_APK =
+ "CtsDuplicatePermissionDeclareApp_SameProtectionLevel.apk";
+ private static final String DUPLICATE_PERMISSION_SAME_PROTECTION_LEVEL_PKG =
+ "com.android.cts.duplicatepermission.sameprotectionlevel";
+
private static final String LOG_TAG = "AppSecurityTests";
@Before
@@ -358,4 +369,34 @@
"appops get " + pkgName + " android:system_alert_window");
}
}
+
+ /**
+ * Tests that a single APK declaring duplicate permissions with different protection levels
+ * cannot be installed.
+ */
+ @Test
+ public void testInstallDuplicatePermission_differentProtectionLevel_fail() throws Exception {
+ try {
+ new InstallMultiple(false /* instant */)
+ .addFile(DUPLICATE_PERMISSION_DIFFERENT_PROTECTION_LEVEL_APK)
+ .runExpectingFailure("INSTALL_PARSE_FAILED_MANIFEST_MALFORMED");
+ } finally {
+ getDevice().uninstallPackage(DUPLICATE_PERMISSION_DIFFERENT_PROTECTION_LEVEL_PKG);
+ }
+ }
+
+ /**
+ * Tests that a single APK declaring duplicate permissions with the same protection level
+ * can be installed.
+ */
+ @Test
+ public void testInstallDuplicatePermission_sameProtectionLevel_success() throws Exception {
+ try {
+ new InstallMultiple(false /* instant */)
+ .addFile(DUPLICATE_PERMISSION_SAME_PROTECTION_LEVEL_APK)
+ .run(true /* expectingSuccess */);
+ } finally {
+ getDevice().uninstallPackage(DUPLICATE_PERMISSION_SAME_PROTECTION_LEVEL_PKG);
+ }
+ }
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApplicationVisibilityTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApplicationVisibilityTest.java
index 787d61e..63718f5 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApplicationVisibilityTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApplicationVisibilityTest.java
@@ -17,10 +17,11 @@
package android.appsecurity.cts;
import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import org.junit.After;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,6 +32,7 @@
/**
* Tests the visibility of installed applications.
*/
+@Presubmit
@RunWith(DeviceJUnit4ClassRunner.class)
public class ApplicationVisibilityTest extends BaseAppSecurityTest {
@@ -319,6 +321,134 @@
testUserId);
}
+ @Test
+ @AppModeFull(reason = "instant applications cannot be granted INTERACT_ACROSS_USERS")
+ public void testGetPackageUidCrossUserGrant() throws Exception {
+ if (!mSupportsMultiUser) {
+ return;
+ }
+
+ final int installUserId = getInstallUserId();
+ final int testUserId = getTestUserId();
+ final Map<String, String> testArgs = new HashMap<>();
+ testArgs.put("testUser", Integer.toString(installUserId));
+
+ installTestAppForUser(TINY_APK, installUserId);
+ // Also install TEST_WITH_PERMISSION_APK in installUSerId for creating across user context
+ installTestAppForUser(TEST_WITH_PERMISSION_APK, installUserId);
+ installTestAppForUser(TEST_WITH_PERMISSION_APK, testUserId);
+
+ Utils.runDeviceTests(
+ getDevice(),
+ TEST_WITH_PERMISSION_PKG,
+ ".ApplicationVisibilityCrossUserTest",
+ "testGetPackageUidVisibility_currentUser",
+ testUserId);
+ Utils.runDeviceTests(
+ getDevice(),
+ TEST_WITH_PERMISSION_PKG,
+ ".ApplicationVisibilityCrossUserTest",
+ "testGetPackageUidVisibility_anotherUserCrossUserGrant",
+ testUserId,
+ testArgs);
+ }
+
+ @Test
+ @AppModeFull(reason = "instant applications cannot see any other application")
+ public void testGetPackageUidCrossUserNoGrant() throws Exception {
+ if (!mSupportsMultiUser) {
+ return;
+ }
+
+ final int installUserId = getInstallUserId();
+ final int testUserId = getTestUserId();
+ final Map<String, String> testArgs = new HashMap<>();
+ testArgs.put("testUser", Integer.toString(installUserId));
+
+ installTestAppForUser(TINY_APK, installUserId);
+ // Also install TEST_WITH_PERMISSION_APK in installUSerId for creating across user context
+ installTestAppForUser(TEST_WITH_PERMISSION_APK, installUserId);
+ installTestAppForUser(TEST_WITH_PERMISSION_APK, testUserId);
+
+ Utils.runDeviceTests(
+ getDevice(),
+ TEST_WITH_PERMISSION_PKG,
+ ".ApplicationVisibilityCrossUserTest",
+ "testGetPackageUidVisibility_currentUser",
+ testUserId);
+ Utils.runDeviceTests(
+ getDevice(),
+ TEST_WITH_PERMISSION_PKG,
+ ".ApplicationVisibilityCrossUserTest",
+ "testGetPackageUidVisibility_anotherUserCrossUserNoGrant",
+ testUserId,
+ testArgs);
+ }
+
+ @Test
+ @AppModeFull(reason = "instant applications cannot be granted INTERACT_ACROSS_USERS")
+ public void testGetPackageGidsCrossUserGrant() throws Exception {
+ if (!mSupportsMultiUser) {
+ return;
+ }
+
+ final int installUserId = getInstallUserId();
+ final int testUserId = getTestUserId();
+ final Map<String, String> testArgs = new HashMap<>();
+ testArgs.put("testUser", Integer.toString(installUserId));
+
+ installTestAppForUser(TINY_APK, installUserId);
+ // Also install TEST_WITH_PERMISSION_APK in installUSerId for creating across user context
+ installTestAppForUser(TEST_WITH_PERMISSION_APK, installUserId);
+ installTestAppForUser(TEST_WITH_PERMISSION_APK, testUserId);
+
+ Utils.runDeviceTests(
+ getDevice(),
+ TEST_WITH_PERMISSION_PKG,
+ ".ApplicationVisibilityCrossUserTest",
+ "testGetPackageGidsVisibility_currentUser",
+ testUserId);
+ Utils.runDeviceTests(
+ getDevice(),
+ TEST_WITH_PERMISSION_PKG,
+ ".ApplicationVisibilityCrossUserTest",
+ "testGetPackageGidsVisibility_anotherUserCrossUserGrant",
+ testUserId,
+ testArgs);
+ }
+
+ @Test
+ @AppModeFull(reason = "instant applications cannot see any other application")
+ public void testGetPackageGidsCrossUserNoGrant() throws Exception {
+ if (!mSupportsMultiUser) {
+ return;
+ }
+
+ final int installUserId = getInstallUserId();
+ final int testUserId = getTestUserId();
+ final Map<String, String> testArgs = new HashMap<>();
+ testArgs.put("testUser", Integer.toString(installUserId));
+
+ installTestAppForUser(TINY_APK, installUserId);
+ // Also install TEST_WITH_PERMISSION_APK in installUSerId for creating across user context
+ installTestAppForUser(TEST_WITH_PERMISSION_APK, installUserId);
+ installTestAppForUser(TEST_WITH_PERMISSION_APK, testUserId);
+
+ Utils.runDeviceTests(
+ getDevice(),
+ TEST_WITH_PERMISSION_PKG,
+ ".ApplicationVisibilityCrossUserTest",
+ "testGetPackageGidsVisibility_currentUser",
+ testUserId);
+ Utils.runDeviceTests(
+ getDevice(),
+ TEST_WITH_PERMISSION_PKG,
+ ".ApplicationVisibilityCrossUserTest",
+ "testGetPackageGidsVisibility_anotherUserCrossUserNoGrant",
+ testUserId,
+ testArgs);
+ }
+
private int getInstallUserId() {
return mUsers[0];
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DeviceIdentifierTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DeviceIdentifierTest.java
index b7d6bd5..ea66311 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DeviceIdentifierTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DeviceIdentifierTest.java
@@ -17,6 +17,7 @@
package android.appsecurity.cts;
import android.platform.test.annotations.AsbSecurityTest;
+
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
index ab51c75..e513aa7 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
@@ -51,7 +51,7 @@
private static final String CLASS = PKG + ".EncryptionAppTest";
private static final String APK = "CtsEncryptionApp.apk";
- private static final String OTHER_APK = "CtsSplitApp29.apk";
+ private static final String OTHER_APK = "CtsSplitApp.apk";
private static final String OTHER_PKG = "com.android.cts.splitapp";
private static final String MODE_NATIVE = "native";
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
index 91ec159..d41257a 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
@@ -22,6 +22,7 @@
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.AsbSecurityTest;
+import android.platform.test.annotations.Presubmit;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -39,6 +40,7 @@
/**
* Tests for ephemeral packages.
*/
+@Presubmit
@RunWith(DeviceJUnit4ClassRunner.class)
@AppModeFull(reason = "Already handles instant installs when needed")
public class EphemeralTest extends BaseAppSecurityTest {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
index 9848f8f..a918540 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
@@ -22,6 +22,8 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import android.platform.test.annotations.Presubmit;
+
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.ddmlib.Log;
import com.android.role.RoleProto;
@@ -29,6 +31,7 @@
import com.android.role.RoleUserStateProto;
import com.android.tradefed.device.CollectingByteOutputReceiver;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.AbiUtils;
@@ -39,7 +42,6 @@
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -51,6 +53,7 @@
/**
* Set of tests that verify behavior of external storage devices.
*/
+@Presubmit
@RunWith(DeviceJUnit4ClassRunner.class)
public class ExternalStorageHostTest extends BaseHostJUnit4Test {
private static final String TAG = "ExternalStorageHostTest";
@@ -111,6 +114,7 @@
private static final String FEATURE_WATCH = "android.hardware.type.watch";
private int[] mUsers;
+ private boolean mAdbWasRoot;
private File getTestAppFile(String fileName) throws FileNotFoundException {
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
@@ -122,6 +126,16 @@
mUsers = Utils.prepareMultipleUsers(getDevice());
assertNotNull(getAbi());
assertNotNull(getBuild());
+
+ ITestDevice device = getDevice();
+ mAdbWasRoot = device.isAdbRoot();
+ if (mAdbWasRoot) {
+ // This test assumes that the test is not run with root privileges. But this test runs
+ // as a part of appsecurity test suite which contains a lot of other tests. Some of
+ // which may enable adb root, make sure that this test is run without root access.
+ device.disableAdbRoot();
+ assertFalse("adb root is enabled", device.isAdbRoot());
+ }
}
@Before
@@ -135,8 +149,25 @@
wipePrimaryExternalStorage();
}
+ @After
+ public void tearDown() throws DeviceNotAvailableException {
+ ITestDevice device = getDevice();
+ if (mAdbWasRoot) {
+ device.enableAdbRoot();
+ } else {
+ device.disableAdbRoot();
+ }
+ }
+
+ // b/215212818 - certain tests fail with fuse-bpf, so temporarily disable
+ private boolean isFuseBpf() throws Exception {
+ return "true".equals(getDevice()
+ .executeShellCommand("getprop persist.sys.fuse.bpf.enable").trim());
+ }
+
@Test
public void testExternalStorageRename() throws Exception {
+ Assume.assumeFalse(isFuseBpf()); // b/215212818
try {
wipePrimaryExternalStorage();
@@ -159,6 +190,7 @@
*/
@Test
public void testExternalStorageNone29() throws Exception {
+ Assume.assumeFalse(isFuseBpf()); // b/215212818
try {
wipePrimaryExternalStorage();
@@ -182,6 +214,7 @@
*/
@Test
public void testExternalStorageRead29() throws Exception {
+ Assume.assumeFalse(isFuseBpf()); // b/215212818
try {
wipePrimaryExternalStorage();
@@ -205,6 +238,7 @@
*/
@Test
public void testExternalStorageWrite() throws Exception {
+ Assume.assumeFalse(isFuseBpf()); // b/215212818
try {
wipePrimaryExternalStorage();
@@ -515,6 +549,55 @@
}
}
+ /**
+ * b/197302116. The apps can't be granted prefix UriPermissions to the uri, when the query
+ * result of the uri is 1.
+ */
+ @Test
+ public void testOwningOneFileNotGrantPrefixUriPermission() throws Exception {
+ Assume.assumeFalse(isFuseBpf()); // b/215212818
+ installPackage(MEDIA.apk);
+
+ int user = getDevice().getCurrentUser();
+
+ // revoke permissions
+ updatePermissions(MEDIA.pkg, user, new String[] {
+ PERM_READ_EXTERNAL_STORAGE,
+ PERM_WRITE_EXTERNAL_STORAGE,
+ }, false);
+
+
+ // revoke the app ops permission
+ updateAppOp(MEDIA.pkg, user, APP_OPS_MANAGE_EXTERNAL_STORAGE, false);
+
+ runDeviceTests(MEDIA.pkg, MEDIA.clazz,
+ "testOwningOneFileNotGrantPrefixUriPermission", user);
+ }
+
+ /**
+ * If the app grants read UriPermission to the uri without id (E.g.
+ * MediaStore.Audio.Media.EXTERNAL_CONTENT_URI), the query result of the uri should be the same
+ * without granting permission.
+ */
+ @Test
+ public void testReadUriPermissionOnUriWithoutId_sameQueryResult() throws Exception {
+ installPackage(MEDIA.apk);
+
+ int user = getDevice().getCurrentUser();
+
+ // revoke permissions
+ updatePermissions(MEDIA.pkg, user, new String[] {
+ PERM_READ_EXTERNAL_STORAGE,
+ PERM_WRITE_EXTERNAL_STORAGE,
+ }, false);
+
+
+ // revoke the app ops permission
+ updateAppOp(MEDIA.pkg, user, APP_OPS_MANAGE_EXTERNAL_STORAGE, false);
+
+ runDeviceTests(MEDIA.pkg, MEDIA.clazz,
+ "testReadUriPermissionOnUriWithoutId_sameQueryResult", user);
+ }
@Test
public void testGrantUriPermission() throws Exception {
@@ -627,20 +710,23 @@
}
@Test
- @Ignore("Enable after b/197701722 is fixed")
public void testMediaEscalation_RequestWriteFilePathSupport() throws Exception {
// Not adding tests for MEDIA_28 and MEDIA_29 as they need W_E_S for write access via file
// path for shared files, and will always have access as they have W_E_S.
installPackage(MEDIA.apk);
int user = getDevice().getCurrentUser();
+ // revoke all permissions
updatePermissions(MEDIA.pkg, user, new String[] {
+ PERM_ACCESS_MEDIA_LOCATION,
PERM_READ_EXTERNAL_STORAGE,
- }, true);
- updatePermissions(MEDIA.pkg, user, new String[] {
PERM_WRITE_EXTERNAL_STORAGE,
}, false);
+ // revoke the app ops permission
+ updateAppOp(MEDIA.pkg, user, APP_OPS_MANAGE_MEDIA, false);
+ updateAppOp(MEDIA.pkg, user, APP_OPS_MANAGE_EXTERNAL_STORAGE, false);
+
runDeviceTests(MEDIA.pkg, MEDIA.clazz, "testMediaEscalation_RequestWriteFilePathSupport",
user);
}
@@ -739,6 +825,7 @@
*/
@Test
public void testCreateRequest_userDenied() throws Exception {
+ Assume.assumeFalse(isFuseBpf());
installPackage(MEDIA.apk);
int user = getDevice().getCurrentUser();
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantAppUserTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantAppUserTest.java
index dee0358..02dbf52 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantAppUserTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantAppUserTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertNull;
import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -34,6 +35,7 @@
/**
* Tests for ephemeral packages.
*/
+@Presubmit
@RunWith(DeviceJUnit4ClassRunner.class)
@AppModeFull(reason = "Already handles instant installs when needed")
public class InstantAppUserTest extends BaseHostJUnit4Test {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
index 6852faf..1980900 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
@@ -17,6 +17,7 @@
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.AppModeInstant;
+import android.platform.test.annotations.Presubmit;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -27,6 +28,7 @@
import java.io.FileNotFoundException;
+@Presubmit
@RunWith(DeviceJUnit4ClassRunner.class)
public class IsolatedSplitsTests extends BaseAppSecurityTest {
private static final String PKG = "com.android.cts.isolatedsplitapp";
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
index 1090b90..6a2f7f8 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
@@ -15,10 +15,11 @@
*/
package android.appsecurity.cts;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -27,6 +28,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.HashMap;
+
+@Presubmit
@RunWith(DeviceJUnit4ClassRunner.class)
@AppModeFull(reason = "Overlays cannot be instant apps")
public class OverlayHostTest extends BaseAppSecurityTest {
@@ -60,12 +64,17 @@
private static final String TEST_APP_APK = "CtsOverlayApp.apk";
private static final String TEST_APP_PACKAGE = "com.android.cts.overlay.app";
private static final String TEST_APP_CLASS = "com.android.cts.overlay.app.OverlayableTest";
+ private static final String OVERLAY_TARGET_TEST_APP_CLASS =
+ "com.android.cts.overlay.target.OverlayTargetTest";
// Overlay states
private static final String STATE_DISABLED = "STATE_DISABLED";
private static final String STATE_ENABLED = "STATE_ENABLED";
private static final String STATE_NO_IDMAP = "STATE_NO_IDMAP";
+ // test arguments
+ private static final String PARAM_START_SERVICE = "start_service";
+
private static final long OVERLAY_WAIT_TIMEOUT = 10000; // 10 seconds
@Before
@@ -157,6 +166,12 @@
}
}
+ private void runDeviceTests(String packageName, String testClassName, String testMethodName,
+ HashMap<String, String> testArgs) throws Exception {
+ Utils.runDeviceTestsAsCurrentUser(getDevice(), packageName, testClassName, testMethodName,
+ testArgs);
+ }
+
/**
* Overlays that target android and are not signed with the platform signature must not be
* installed successfully.
@@ -313,4 +328,68 @@
runOverlayDeviceTest(TARGET_OVERLAYABLE_APK, OVERLAY_ALL_APK, OVERLAY_ALL_PACKAGE,
testMethod);
}
+
+ @Test
+ public void testOverlayEnabled_activityInForeground() throws Exception {
+ final HashMap<String, String> testArgs = new HashMap<>();
+ testArgs.put(PARAM_START_SERVICE, Boolean.FALSE.toString());
+ try {
+ new InstallMultiple().addFile(OVERLAY_ALL_APK).run();
+ new InstallMultiple().addFile(TARGET_OVERLAYABLE_APK).run();
+
+ runDeviceTests(TARGET_PACKAGE, OVERLAY_TARGET_TEST_APP_CLASS,
+ "overlayEnabled_activityInForeground", testArgs);
+ } finally {
+ getDevice().uninstallPackage(TARGET_PACKAGE);
+ getDevice().uninstallPackage(OVERLAY_ALL_PACKAGE);
+ }
+ }
+
+ @Test
+ public void testOverlayEnabled_activityInBackground_toForeground() throws Exception {
+ final HashMap<String, String> testArgs = new HashMap<>();
+ testArgs.put(PARAM_START_SERVICE, Boolean.FALSE.toString());
+ try {
+ new InstallMultiple().addFile(OVERLAY_ALL_APK).run();
+ new InstallMultiple().addFile(TARGET_OVERLAYABLE_APK).run();
+
+ runDeviceTests(TARGET_PACKAGE, OVERLAY_TARGET_TEST_APP_CLASS,
+ "overlayEnabled_activityInBackground_toForeground", testArgs);
+ } finally {
+ getDevice().uninstallPackage(TARGET_PACKAGE);
+ getDevice().uninstallPackage(OVERLAY_ALL_PACKAGE);
+ }
+ }
+
+ @Test
+ public void testOverlayEnabled_activityWithServiceInForeground() throws Exception {
+ final HashMap<String, String> testArgs = new HashMap<>();
+ testArgs.put(PARAM_START_SERVICE, Boolean.TRUE.toString());
+ try {
+ new InstallMultiple().addFile(OVERLAY_ALL_APK).run();
+ new InstallMultiple().addFile(TARGET_OVERLAYABLE_APK).run();
+
+ runDeviceTests(TARGET_PACKAGE, OVERLAY_TARGET_TEST_APP_CLASS,
+ "overlayEnabled_activityInForeground", testArgs);
+ } finally {
+ getDevice().uninstallPackage(TARGET_PACKAGE);
+ getDevice().uninstallPackage(OVERLAY_ALL_PACKAGE);
+ }
+ }
+
+ @Test
+ public void testOverlayEnabled_activityWithServiceInBackground_toForeground() throws Exception {
+ final HashMap<String, String> testArgs = new HashMap<>();
+ testArgs.put(PARAM_START_SERVICE, Boolean.TRUE.toString());
+ try {
+ new InstallMultiple().addFile(OVERLAY_ALL_APK).run();
+ new InstallMultiple().addFile(TARGET_OVERLAYABLE_APK).run();
+
+ runDeviceTests(TARGET_PACKAGE, OVERLAY_TARGET_TEST_APP_CLASS,
+ "overlayEnabled_activityInBackground_toForeground", testArgs);
+ } finally {
+ getDevice().uninstallPackage(TARGET_PACKAGE);
+ getDevice().uninstallPackage(OVERLAY_ALL_PACKAGE);
+ }
+ }
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java
index c932908..0e177bb 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java
@@ -21,6 +21,8 @@
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.AppModeInstant;
+import android.platform.test.annotations.Presubmit;
+
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import org.junit.After;
@@ -31,6 +33,7 @@
/**
* Tests for visibility of packages installed in one user, in a different user.
*/
+@Presubmit
@RunWith(DeviceJUnit4ClassRunner.class)
public class PackageVisibilityTest extends BaseAppSecurityTest {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
index c110f7b..2e1c41e 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
@@ -17,6 +17,7 @@
package android.appsecurity.cts;
import android.platform.test.annotations.AsbSecurityTest;
+import android.platform.test.annotations.Presubmit;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.build.IBuildInfo;
@@ -36,6 +37,7 @@
/**
* Tests for APK signature verification during installation.
*/
+@Presubmit
public class PkgInstallSignatureVerificationTest extends DeviceTestCase implements IBuildReceiver {
private static final String TEST_PKG = "android.appsecurity.cts.tinyapp";
@@ -705,6 +707,93 @@
"verifySignatures_withRotation_succeeds");
}
+ public void testInstallV31UpdateAfterRotation() throws Exception {
+ // This test is the same as above, but using the v3.1 signature scheme for rotation.
+ assertInstallFromBuildSucceeds("CtsSignatureQueryService.apk");
+ assertInstallFromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
+ Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+ "verifySignatures_noRotation_succeeds");
+
+ assertInstallSucceeds("CtsSignatureQueryService_v2-tgt-33.apk");
+ Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+ "verifySignatures_withRotation_succeeds");
+
+ assertInstallSucceeds("CtsSignatureQueryService_v3-tgt-33.apk");
+ assertInstallSucceeds("CtsSignatureQueryServiceTest_v2-tgt-33.apk");
+ Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+ "verifySignatures_withRotation_succeeds");
+ }
+
+ public void testInstallV41UpdateAfterRotation() throws Exception {
+ // V4 is only enabled on devices with Incremental feature
+ if (!hasIncrementalFeature()) {
+ return;
+ }
+
+ // This test is the same as above, but using the v4.1 signature scheme for rotation.
+ assertInstallV4FromBuildSucceeds("CtsSignatureQueryService.apk");
+ assertInstallV4FromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
+ Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+ "verifySignatures_noRotation_succeeds");
+
+ assertInstallV4Succeeds("CtsSignatureQueryService_v2-tgt-33.apk");
+ Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+ "verifySignatures_withRotation_succeeds");
+
+ assertInstallV4Succeeds("CtsSignatureQueryService_v3-tgt-33.apk");
+ assertInstallV4Succeeds("CtsSignatureQueryServiceTest_v2-tgt-33.apk");
+ Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+ "verifySignatures_withRotation_succeeds");
+ }
+
+ public void testInstallV41WrongBlockId() throws Exception {
+ // V4 is only enabled on devices with Incremental feature
+ if (!hasIncrementalFeature()) {
+ return;
+ }
+
+ // This test is the same as above, but using the v4.1 signature scheme for rotation.
+ assertInstallV4FromBuildSucceeds("CtsSignatureQueryService.apk");
+ assertInstallV4FromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
+ Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+ "verifySignatures_noRotation_succeeds");
+
+ assertInstallV4FailsWithError("CtsSignatureQueryService_v2-tgt-33-wrongV41Block.apk",
+ "Failed to find V4 signature block corresponding to V3 blockId: 462663009");
+ }
+
+ public void testInstallV41LegacyV4() throws Exception {
+ // V4 is only enabled on devices with Incremental feature
+ if (!hasIncrementalFeature()) {
+ return;
+ }
+
+ // This test is the same as above, but using the v4.1 signature scheme for rotation.
+ assertInstallV4FromBuildSucceeds("CtsSignatureQueryService.apk");
+ assertInstallV4FromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
+ Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+ "verifySignatures_noRotation_succeeds");
+
+ assertInstallV4FailsWithError("CtsSignatureQueryService_v2-tgt-33-legacyV4.apk",
+ "Failed to find V4 signature block corresponding to V3 blockId: 462663009");
+ }
+
+ public void testInstallV41WrongDigest() throws Exception {
+ // V4 is only enabled on devices with Incremental feature
+ if (!hasIncrementalFeature()) {
+ return;
+ }
+
+ // This test is the same as above, but using the v4.1 signature scheme for rotation.
+ assertInstallV4FromBuildSucceeds("CtsSignatureQueryService.apk");
+ assertInstallV4FromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
+ Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+ "verifySignatures_noRotation_succeeds");
+
+ assertInstallV4FailsWithError("CtsSignatureQueryService_v2-tgt-33-wrongDigest.apk",
+ "APK digest in V4 signature does not match V2/V3");
+ }
+
public void testInstallV3KeyRotationSigPerm() throws Exception {
// tests that a v3 signed APK can still get a signature permission from an app with its
// older signing certificate.
@@ -988,6 +1077,110 @@
Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
}
+ public void testV31TargetTPlatformUsesRotatedKey() throws Exception {
+ // The v3.1 signature block is intended to allow applications to target T+ for APK signing
+ // key rotation without needing multi-targeting APKs. This test verifies a standard APK
+ // install with the rotated key in the v3.1 signing block targeting T is recognized by the
+ // platform, and this rotated key is used as the signing identity.
+ assertInstallSucceeds("v31-ec-p256_2-tgt-33.apk");
+ Utils.runDeviceTests(
+ getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
+ "testUsingRotatedSigner");
+ }
+
+ public void testV31TargetLaterThanDevicePlatformUsesOriginalKey() throws Exception {
+ // The v3.1 signature block allows targeting SDK versions later than T for rotation; for
+ // this test a target of 100001 is used assuming it will be beyond the platform's version.
+ // Since the target version for rotation is beyond the platform's version the original
+ // signer from the v3.0 block should be used.
+ assertInstallSucceeds("v31-ec-p256_2-tgt-100001.apk");
+ Utils.runDeviceTests(
+ getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
+ "testUsingOriginalSigner");
+ }
+
+ public void testV31BlockStrippedWithV3StrippingProtectionAttrSet() throws Exception {
+ // With the introduction of the v3.1 signature scheme, a new stripping protection attribute
+ // has been added to the v3.0 signer to protect against stripping and modification of the
+ // v3.1 signing block. This test verifies a stripped v3.1 block is detected when the v3.0
+ // stripping protection attribute is set.
+ assertInstallFails("v31-block-stripped-v3-attr-value-33.apk");
+ }
+
+ public void testV31BlockWithMultipleSignersUsesCorrectSigner() throws Exception {
+ // All of the APKs for this test use multiple v3.1 signers; those targeting SDK versions
+ // expected to be outside the version of a device under test use the original signer, and
+ // those targeting an expected range for a device use the rotated key. This test is
+ // intended to ensure the signer with the min / max SDK version that matches the device
+ // SDK version is used.
+
+ // The APK used for this test contains two signers in the v3.1 signing block. The first
+ // has a range from 100001 to Integer.MAX_VALUE and is using the original signing key;
+ // the second targets 33 to 100000 using the rotated key.
+ assertInstallSucceeds("v31-ec-p256_2-tgt-33-ec-p256-tgt-100001.apk");
+ Utils.runDeviceTests(
+ getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
+ "testUsingRotatedSigner");
+ uninstallPackage();
+
+ // The APK for this test contains two signers in the v3.1 block, one targeting SDK versions
+ // 1 to 27 using the original signer, and the other targeting 33+ using the rotated signer.
+ assertInstallSucceeds("v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27.apk");
+ Utils.runDeviceTests(
+ getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
+ "testUsingRotatedSigner");
+ uninstallPackage();
+
+ // This APK combines the extra signers from the APKs above, one targeting 1 to 27 with the
+ // original signing key, another targeting 100001+ with the original signing key, and the
+ // last targeting 33 to 100000 with the rotated key.
+ assertInstallSucceeds("v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27-and-100001.apk");
+ Utils.runDeviceTests(
+ getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
+ "testUsingRotatedSigner");
+ }
+
+ public void testV31UpdateV3ToFromV31Succeeds() throws Exception {
+ // Since the v3.1 block is just intended to allow targeting SDK versions T and later for
+ // rotation, an APK signed with the rotated key in a v3.0 signing block should support
+ // updates to an APK signed with the same signing key in a v3.1 signing block.
+ assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-default-caps.apk");
+ assertInstallSucceeds("v31-ec-p256_2-tgt-33.apk");
+ uninstallPackage();
+
+ // Similarly an APK signed with the rotated key in a v3.1 signing block should support
+ // updates to an APK signed with the same signing key in a v3.0 signing block.
+ assertInstallSucceeds("v31-ec-p256_2-tgt-33.apk");
+ assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-default-caps.apk");
+ uninstallPackage();
+ }
+
+ public void testV31RotationTargetModifiedReportedByV3() throws Exception {
+ // When determining if a signer in the v3.1 signing block should be applied, the min / max
+ // SDK versions from the signer are compared against the device's SDK version; if the device
+ // is not within the signer's range then the block is skipped, other v3.1 blocks are
+ // checked, and finally the v3.0 block is used. The v3.0 signer block contains an additional
+ // attribute with the rotation-min-sdk-version that was expected in the v3.1 signing
+ // block; if this attribute's value does not match what was found in the v3.1 block the
+ // APK should fail to install.
+ assertInstallFails("v31-ec-p256_2-tgt-33-modified.apk");
+ }
+
+ public void testV31RotationTargetsDevRelease() throws Exception {
+ // The v3.1 signature scheme allows targeting a platform release under development through
+ // the use of a rotation-targets-dev-release additional attribute. Since a platform under
+ // development shares the same SDK version as the most recently released platform, the
+ // attribute is used by the platform to determine if a signer block should be applied. If
+ // the signer's minSdkVersion is the same as the device's SDK version and this attribute
+ // is set, then the platform will check the value of ro.build.version.codename; a value of
+ // "REL" indicates the platform is a release platform, so the current signer block will not
+ // be used. During T's development, the SDK version is 31 and the codename is not "REL", so
+ // this test APK will install on T during development as well as after its release since
+ // the SDK version will be bumped at that point.
+ assertInstallSucceeds("v31-ec-p256_2-tgt-31-dev-release.apk");
+ }
+
+
public void testInstallTargetSdk30WithV1Signers() throws Exception {
// An app targeting SDK version >= 30 must have at least a V2 signature; this test verifies
// an app targeting SDK version 30 with only a V1 signature fails to install.
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java
index 9b0a243..2e569a5 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java
@@ -18,6 +18,7 @@
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.LargeTest;
+import android.platform.test.annotations.Presubmit;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.ddmlib.Log;
@@ -32,6 +33,7 @@
/**
* Tests that verify intent filters.
*/
+@Presubmit
@LargeTest
@AppModeFull(reason="Instant applications can never be system or privileged")
public class PrivilegedUpdateTests extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
@@ -178,6 +180,25 @@
}
}
+ public void testUninstallDisabledUpdatedSystemApp_remainingDisabled() throws Exception {
+ if (!isDefaultAbi()) {
+ Log.w(TAG, "Skipping test for non-default abi.");
+ return;
+ }
+
+ getDevice().executeShellCommand("pm enable " + SHIM_PKG);
+ runDeviceTests(TEST_PKG, ".PrivilegedAppDisableTest", "testPrivAppAndEnabled");
+ try {
+ assertNull(getDevice().installPackage(
+ mBuildHelper.getTestFile(SHIM_UPDATE_APK), true));
+ getDevice().executeShellCommand("pm disable-user " + SHIM_PKG);
+ runDeviceTests(TEST_PKG, ".PrivilegedAppDisableTest", "testUpdatedPrivAppAndDisabled");
+ } finally {
+ getDevice().uninstallPackage(SHIM_PKG);
+ }
+ runDeviceTests(TEST_PKG, ".PrivilegedAppDisableTest", "testPrivAppAndDisabled");
+ }
+
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/ReadableSettingsFieldsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ReadableSettingsFieldsTest.java
index 53d81c3..9b2d612 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ReadableSettingsFieldsTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ReadableSettingsFieldsTest.java
@@ -45,6 +45,9 @@
private static final String TEST_CLASS = TEST_PACKAGE + ".ReadSettingsFieldsTest";
private static final String TEST_APK = "CtsReadSettingsFieldsApp.apk";
private static final String TEST_APK_TEST_ONLY = "CtsReadSettingsFieldsAppTestOnly.apk";
+ private static final String TEST_APK_TARGET_Q = "CtsReadSettingsFieldsAppTargetQ.apk";
+ private static final String TEST_APK_TARGET_R = "CtsReadSettingsFieldsAppTargetR.apk";
+ private static final String TEST_APK_TARGET_S = "CtsReadSettingsFieldsAppTargetS.apk";
@Before
public void setUp() throws Exception {
@@ -140,6 +143,25 @@
}
@Test
+ public void testSettingsKeysNotReadableForAfterR()
+ throws DeviceNotAvailableException, FileNotFoundException {
+ new InstallMultiple().addFile(TEST_APK_TARGET_S).run();
+ runDeviceTests(TEST_PACKAGE, TEST_CLASS,
+ "testSettingsKeysNotReadableForAfterR");
+ }
+
+ @Test
+ public void testSettingsKeysReadableForRMinus()
+ throws DeviceNotAvailableException, FileNotFoundException {
+ new InstallMultiple().addFile(TEST_APK_TARGET_R).run();
+ runDeviceTests(TEST_PACKAGE, TEST_CLASS,
+ "testSettingsKeysReadableForRMinus");
+ new InstallMultiple().addFile(TEST_APK_TARGET_Q).run();
+ runDeviceTests(TEST_PACKAGE, TEST_CLASS,
+ "testSettingsKeysReadableForRMinus");
+ }
+
+ @Test
public void testQueryGlobalSettingsNoHiddenKeysWithoutAnnotation()
throws DeviceNotAvailableException {
runDeviceTests(TEST_PACKAGE, TEST_CLASS,
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
index db6124a..3bebcbc 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
@@ -57,7 +57,7 @@
private static final String CLASS = PKG + ".EncryptionAppTest";
private static final String APK = "CtsEncryptionApp.apk";
- private static final String OTHER_APK = "CtsSplitApp29.apk";
+ private static final String OTHER_APK = "CtsSplitApp.apk";
private static final String OTHER_PKG = "com.android.cts.splitapp";
private static final String FEATURE_REBOOT_ESCROW = "feature:android.hardware.reboot_escrow";
@@ -422,8 +422,8 @@
private void deviceDisableDeviceConfigSync() throws Exception {
getDevice().executeShellCommand("device_config set_sync_disabled_for_tests persistent");
- String res = getDevice().executeShellCommand("device_config is_sync_disabled_for_tests");
- if (res == null || !res.contains("true")) {
+ String res = getDevice().executeShellCommand("device_config get_sync_disabled_for_tests");
+ if (res == null || !res.contains("persistent")) {
CLog.w(TAG, "Could not disable device config for test");
}
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SharedUserIdTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SharedUserIdTest.java
index 8d1024a..b9b16ce 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/SharedUserIdTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SharedUserIdTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertNotNull;
import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
import com.android.ddmlib.Log;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -30,6 +31,7 @@
/**
* Set of tests that verify behavior related to apps with shared user IDs
*/
+@Presubmit
@RunWith(DeviceJUnit4ClassRunner.class)
public class SharedUserIdTest extends BaseAppSecurityTest {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
index 5ed4aea..4d0150e 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
@@ -20,6 +20,7 @@
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.AppModeInstant;
+import android.platform.test.annotations.Presubmit;
import com.android.compatibility.common.util.CpuFeatures;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -41,6 +42,7 @@
/**
* Tests that verify installing of various split APKs from host side.
*/
+@Presubmit
@RunWith(DeviceJUnit4ClassRunner.class)
public class SplitTests extends BaseAppSecurityTest {
static final String PKG_NO_RESTART = "com.android.cts.norestart";
@@ -51,6 +53,19 @@
static final String APK_NEED_SPLIT_FEATURE_WARM = "CtsNeedSplitFeatureWarm.apk";
static final String APK_NEED_SPLIT_CONFIG = "CtsNeedSplitApp_xxhdpi-v4.apk";
+ static final String APK_REQUIRED_SPLIT_TYPE_BASE = "CtsRequiredSplitTypeSplitApp.apk";
+ static final String APK_REQUIRED_SPLIT_TYPE_BASE_UPDATED =
+ "CtsRequiredSplitTypeSplitAppUpdated.apk";
+ static final String APK_REQUIRED_SPLIT_TYPE_DENSITY = "CtsSplitAppTypeDensity.apk";
+ static final String APK_REQUIRED_SPLIT_TYPE_LOCALE = "CtsSplitAppTypeLocale.apk";
+ static final String APK_REQUIRED_SPLIT_TYPE_FOO = "CtsSplitAppTypeFoo.apk";
+ static final String APK_REQUIRED_SPLIT_TYPE_MULTIPLE = "CtsSplitAppTypeMultiple.apk";
+ static final String APK_REQUIRED_SPLIT_TYPE_FEATURE = "CtsSplitAppTypeFeature.apk";
+ static final String APK_REQUIRED_SPLIT_TYPE_FEATURE_DATA = "CtsSplitAppTypeFeatureData.apk";
+ static final String APK_REQUIRED_SPLIT_TYPE_FEATURE_FOO = "CtsSplitAppTypeFeatureFoo.apk";
+ static final String APK_INVALID_REQUIRED_SPLIT_TYPE_BASE =
+ "CtsInvalidRequiredSplitTypeSplitApp.apk";
+
static final String PKG = "com.android.cts.splitapp";
static final String CLASS = PKG + ".SplitAppTest";
@@ -710,6 +725,170 @@
.runExpectingFailure("INSTALL_FAILED_MISSING_SPLIT");
}
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testRequiredSplitTypesInstalledIncomplete_full() throws Exception {
+ testRequiredSplitTypesInstalledIncomplete(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testRequiredSplitTypesInstalledIncomplete_instant() throws Exception {
+ testRequiredSplitTypesInstalledIncomplete(true);
+ }
+ private void testRequiredSplitTypesInstalledIncomplete(boolean instant) throws Exception {
+ new InstallMultiple(instant).addFile(APK_REQUIRED_SPLIT_TYPE_BASE)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_LOCALE)
+ .runExpectingFailure("INSTALL_FAILED_MISSING_SPLIT");
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testInvalidRequiredSplitTypes_full() throws Exception {
+ testInvalidRequiredSplitTypes(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testInvalidRequiredSplitTypes_instant() throws Exception {
+ testInvalidRequiredSplitTypes(true);
+ }
+ private void testInvalidRequiredSplitTypes(boolean instant) throws Exception {
+ new InstallMultiple(instant).addFile(APK_INVALID_REQUIRED_SPLIT_TYPE_BASE)
+ .runExpectingFailure("INSTALL_PARSE_FAILED_MANIFEST_MALFORMED");
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testRequiredSplitTypesInstalledAll_full() throws Exception {
+ testRequiredSplitTypesInstalledAll(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testRequiredSplitTypesInstalledAll_instant() throws Exception {
+ testRequiredSplitTypesInstalledAll(true);
+ }
+ private void testRequiredSplitTypesInstalledAll(boolean instant) throws Exception {
+ new InstallMultiple(instant).addFile(APK_REQUIRED_SPLIT_TYPE_BASE)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_DENSITY)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_LOCALE)
+ .run();
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testRequiredSplitTypesInstalledMultipleOne_full() throws Exception {
+ testRequiredSplitTypesInstalledMultipleOne(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testRequiredSplitTypesInstalledMultipleOne_instant() throws Exception {
+ testRequiredSplitTypesInstalledMultipleOne(true);
+ }
+ private void testRequiredSplitTypesInstalledMultipleOne(boolean instant) throws Exception {
+ new InstallMultiple(instant).addFile(APK_REQUIRED_SPLIT_TYPE_BASE)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_MULTIPLE)
+ .run();
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testRequiredSplitTypesRemoved_full() throws Exception {
+ testRequiredSplitTypesRemoved(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testRequiredSplitTypesRemoved_instant() throws Exception {
+ testRequiredSplitTypesRemoved(true);
+ }
+ private void testRequiredSplitTypesRemoved(boolean instant) throws Exception {
+ // start with a base and three splits
+ new InstallMultiple(instant)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_BASE)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_DENSITY)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_LOCALE)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_FOO)
+ .run();
+ // it's okay to remove non-required split type
+ new InstallMultiple(instant).inheritFrom(PKG).removeSplit("config.foo").run();
+ // but, not to remove a required one
+ new InstallMultiple(instant).inheritFrom(PKG).removeSplit("config.de")
+ .runExpectingFailure("INSTALL_FAILED_MISSING_SPLIT");
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testInheritUpdatedBase_requiredSplitTypesMissing_full() throws Exception {
+ testInheritUpdatedBase_requiredSplitTypesMissing(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testInheritUpdatedBase_requiredSplitTypesMissing_instant() throws Exception {
+ testInheritUpdatedBase_requiredSplitTypesMissing(true);
+ }
+ private void testInheritUpdatedBase_requiredSplitTypesMissing(boolean instant)
+ throws Exception {
+ // start with a base and the required splits
+ new InstallMultiple(instant)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_BASE)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_DENSITY)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_LOCALE)
+ .run();
+ // the updated base requires a new split type, but it's missing
+ new InstallMultiple(instant).inheritFrom(PKG).addFile(APK_REQUIRED_SPLIT_TYPE_BASE_UPDATED)
+ .runExpectingFailure("INSTALL_FAILED_MISSING_SPLIT");
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testInheritUpdatedBase_requiredSplitTypesInstalled_full() throws Exception {
+ testInheritUpdatedBase_requiredSplitTypesInstalled(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testInheritUpdatedBase_requiredSplitTypesInstalled_instant() throws Exception {
+ testInheritUpdatedBase_requiredSplitTypesInstalled(true);
+ }
+ private void testInheritUpdatedBase_requiredSplitTypesInstalled(boolean instant)
+ throws Exception {
+ // start with a base and the required splits
+ new InstallMultiple(instant)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_BASE)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_DENSITY)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_LOCALE)
+ .run();
+ // the updated base requires a split type, and this split also requires another.
+ new InstallMultiple(instant).inheritFrom(PKG)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_BASE_UPDATED)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_FEATURE)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_FEATURE_DATA)
+ .run();
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testRequiredSplitTypesFromSplit_full() throws Exception {
+ testRequiredSplitTypesFromSplit(false);
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testRequiredSplitTypesFromSplit_instant() throws Exception {
+ testRequiredSplitTypesFromSplit(true);
+ }
+ private void testRequiredSplitTypesFromSplit(boolean instant) throws Exception {
+ new InstallMultiple(instant)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_BASE)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_DENSITY)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_LOCALE)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_FEATURE)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_FEATURE_DATA)
+ .addFile(APK_REQUIRED_SPLIT_TYPE_FEATURE_FOO)
+ .run();
+ // it's okay to remove non-required split type for the split
+ new InstallMultiple(instant).inheritFrom(PKG).removeSplit("feature_foo.foo").run();
+ // but, not to remove a required one for the split
+ new InstallMultiple(instant).inheritFrom(PKG).removeSplit("feature_foo.data")
+ .runExpectingFailure("INSTALL_FAILED_MISSING_SPLIT");
+ }
+
/**
* Verify that installing a new version of app wipes code cache.
*/
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
index 7f4cf27..ff1c433 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
@@ -16,6 +16,8 @@
package android.appsecurity.cts;
+import static org.junit.Assume.assumeTrue;
+
import com.android.ddmlib.testrunner.TestResult.TestStatus;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.result.TestDescription;
@@ -31,7 +33,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import static org.junit.Assume.assumeTrue;
import java.util.Map;
@@ -43,11 +44,15 @@
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";
+ private static final String PKG_NO_APP_STORAGE = "com.android.cts.noappstorage";
private static final String APK_STATS = "CtsStorageStatsApp.apk";
private static final String APK_A = "CtsStorageAppA.apk";
private static final String APK_B = "CtsStorageAppB.apk";
+ private static final String APK_NO_APP_STORAGE = "CtsNoAppDataStorageApp.apk";
private static final String CLASS_STATS = "com.android.cts.storagestatsapp.StorageStatsTest";
private static final String CLASS = "com.android.cts.storageapp.StorageTest";
+ private static final String CLASS_NO_APP_STORAGE =
+ "com.android.cts.noappstorage.NoAppDataStorageTest";
private int[] mUsers;
@@ -58,6 +63,7 @@
installPackage(APK_STATS);
installPackage(APK_A);
installPackage(APK_B);
+ installPackage(APK_NO_APP_STORAGE);
for (int user : mUsers) {
getDevice().executeShellCommand("appops set --user " + user + " " + PKG_STATS
@@ -72,6 +78,7 @@
getDevice().uninstallPackage(PKG_STATS);
getDevice().uninstallPackage(PKG_A);
getDevice().uninstallPackage(PKG_B);
+ getDevice().uninstallPackage(PKG_NO_APP_STORAGE);
}
@Test
@@ -223,6 +230,16 @@
}
}
+ @Test
+ public void testNoInternalAppStorage() throws Exception {
+ for (int user : mUsers) {
+ runDeviceTests(
+ PKG_NO_APP_STORAGE, CLASS_NO_APP_STORAGE, "testNoInternalCeStorage", user);
+ runDeviceTests(
+ PKG_NO_APP_STORAGE, CLASS_NO_APP_STORAGE, "testNoInternalDeStorage", user);
+ }
+ }
+
public void waitForIdle() throws Exception {
// Try getting all pending events flushed out
for (int i = 0; i < 4; i++) {
diff --git a/hostsidetests/appsecurity/test-apps/ApplicationVisibilityCrossUserApp/src/com/android/cts/applicationvisibility/ApplicationVisibilityCrossUserTest.java b/hostsidetests/appsecurity/test-apps/ApplicationVisibilityCrossUserApp/src/com/android/cts/applicationvisibility/ApplicationVisibilityCrossUserTest.java
index 042e74a..0e81d2b 100644
--- a/hostsidetests/appsecurity/test-apps/ApplicationVisibilityCrossUserApp/src/com/android/cts/applicationvisibility/ApplicationVisibilityCrossUserTest.java
+++ b/hostsidetests/appsecurity/test-apps/ApplicationVisibilityCrossUserApp/src/com/android/cts/applicationvisibility/ApplicationVisibilityCrossUserTest.java
@@ -194,6 +194,100 @@
} catch (SecurityException e) {}
}
+ /**
+ * Tests getting the uid of the installed packages for the current user.
+ **/
+ @Test
+ public void testGetPackageUidVisibility_currentUser() {
+ final PackageManager pm = mContext.getPackageManager();
+ try {
+ pm.getPackageUid(TINY_PKG, 0 /*flags*/);
+ fail("Should have received a NameNotFoundException");
+ } catch (PackageManager.NameNotFoundException e) {
+ // Expected
+ }
+ }
+
+ /**
+ * Tests getting the uid of the installed packages for primary user,
+ * with cross user permission granted.
+ **/
+ @Test
+ public void testGetPackageUidVisibility_anotherUserCrossUserGrant() {
+ final PackageManager pm = mContext.createContextAsUser(UserHandle.of(getTestUser()),
+ 0 /*flags*/).getPackageManager();
+ try {
+ pm.getPackageUid(TINY_PKG, MATCH_KNOWN_PACKAGES);
+ } catch (PackageManager.NameNotFoundException e) {
+ fail("Should not receive a NameNotFoundException");
+ }
+ }
+
+ /**
+ * Tests getting the uid of the installed packages for primary user,
+ * with cross user permission revoked.
+ **/
+ @Test
+ public void testGetPackageUidVisibility_anotherUserCrossUserNoGrant()
+ throws PackageManager.NameNotFoundException {
+ final PackageManager pm = mContext.createContextAsUser(UserHandle.of(getTestUser()),
+ 0 /*flags*/).getPackageManager();
+ ungrantAcrossUsersPermission();
+ try {
+ pm.getPackageUid(TINY_PKG, MATCH_KNOWN_PACKAGES);
+ fail("Should have received a SecurityException");
+ } catch (SecurityException e) {
+ // Expected
+ }
+ }
+
+ /**
+ * Tests getting the gids of the installed packages for the current user.
+ **/
+ @Test
+ public void testGetPackageGidsVisibility_currentUser() {
+ final PackageManager pm = mContext.getPackageManager();
+ try {
+ pm.getPackageGids(TINY_PKG, 0 /*flags*/);
+ fail("Should have received a NameNotFoundException");
+ } catch (PackageManager.NameNotFoundException e) {
+ // Expected
+ }
+ }
+
+ /**
+ * Tests getting the gids of the installed packages for primary user,
+ * with cross user permission granted.
+ **/
+ @Test
+ public void testGetPackageGidsVisibility_anotherUserCrossUserGrant() {
+ final PackageManager pm = mContext.createContextAsUser(UserHandle.of(getTestUser()),
+ 0 /*flags*/).getPackageManager();
+ try {
+ pm.getPackageUid(TINY_PKG, MATCH_KNOWN_PACKAGES);
+ } catch (PackageManager.NameNotFoundException e) {
+ fail("Should not receive a NameNotFoundException");
+ }
+ }
+
+ /**
+ * Tests getting the gids of the installed packages for primary user,
+ * with cross user permission revoked.
+ **/
+ @Test
+ public void testGetPackageGidsVisibility_anotherUserCrossUserNoGrant()
+ throws PackageManager.NameNotFoundException {
+ final PackageManager pm = mContext.createContextAsUser(UserHandle.of(getTestUser()),
+ 0 /*flags*/).getPackageManager();
+ ungrantAcrossUsersPermission();
+ try {
+ pm.getPackageUid(TINY_PKG, MATCH_KNOWN_PACKAGES);
+ fail("Should have received a SecurityException");
+ } catch (SecurityException e) {
+ // Expected
+ }
+ }
+
private boolean isAppInPackageList(String packageName,
List<PackageInfo> packageList) {
for (PackageInfo pkgInfo : packageList) {
diff --git a/hostsidetests/appsecurity/test-apps/CorruptApkTests/OWNERS b/hostsidetests/appsecurity/test-apps/CorruptApkTests/OWNERS
index 21cd9d9..870d1a4 100644
--- a/hostsidetests/appsecurity/test-apps/CorruptApkTests/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/CorruptApkTests/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 568761
-rtmitchell@google.com
+zyy@google.com
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
index ef54b1a..ed22bab 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
@@ -23,6 +23,7 @@
defaults: ["cts_support_defaults"],
sdk_version: "test_current",
static_libs: [
+ "androidx.appcompat_appcompat",
"androidx.test.rules",
"compatibility-device-util-axt",
"ctstestrunner-axt",
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
index 1fecc44..0df4795 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
@@ -46,6 +46,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.os.BuildCompat;
import com.android.cts.documentclient.MyActivity.Result;
@@ -71,6 +72,7 @@
private static final String TEST_TARGET_DIRECTORY_PATH =
TEST_SOURCE_DIRECTORY_PATH + File.separatorChar + TEST_TARGET_DIRECTORY_NAME;
private static final String STORAGE_AUTHORITY = "com.android.externalstorage.documents";
+ private static final String DEFAULT_DEVICE_NAME = "Internal storage";
private UiSelector findRootListSelector() throws UiObjectNotFoundException {
return new UiSelector().resourceId(
@@ -173,19 +175,19 @@
return new UiObject(new UiSelector().resourceId("android:id/button1"));
}
- private void assertToolbarTitleEquals(String label) throws UiObjectNotFoundException {
+ private boolean checkToolbarTitleEquals(String label) throws UiObjectNotFoundException {
final UiObject title = new UiObject(new UiSelector().resourceId(
getDocumentsUiPackageId() + ":id/toolbar").childSelector(
new UiSelector().className("android.widget.TextView").text(label)));
- assertTrue(title.waitForExists(TIMEOUT));
+ return title.waitForExists(TIMEOUT);
}
private String getDeviceName() {
final String deviceName = Settings.Global.getString(
mActivity.getContentResolver(), Settings.Global.DEVICE_NAME);
- // Device name should always be set. In case it isn't, fall back to "Internal Storage"
- return !TextUtils.isEmpty(deviceName) ? deviceName : "Internal Storage";
+ // Device name should always be set. In case it isn't, fall back to "Internal storage"
+ return !TextUtils.isEmpty(deviceName) ? deviceName : DEFAULT_DEVICE_NAME;
}
@Override
@@ -431,15 +433,25 @@
// save button is disabled for the storage root
assertFalse(findSaveButton().isEnabled());
- // We should always have Android directory available
- findDocument("Android").click();
- mDevice.waitForIdle();
+ // SAF directory access to /Android is blocked in ag/13163842, this change may not be
+ // present on some R devices, so only test it from above S.
+ if (BuildCompat.isAtLeastS()) {
+ // We should always have Android directory available
+ findDocument("Android").click();
+ mDevice.waitForIdle();
- // save button is disabled for Android folder
- assertFalse(findSaveButton().isEnabled());
+ // save button is disabled for Android folder
+ assertFalse(findSaveButton().isEnabled());
+ }
- findRoot(getDeviceName()).click();
- mDevice.waitForIdle();
+ try {
+ findRoot(getDeviceName()).click();
+ mDevice.waitForIdle();
+ } catch(UiObjectNotFoundException e) {
+ // It might be possible that OEMs customize storage root to "Internal storage".
+ findRoot(DEFAULT_DEVICE_NAME).click();
+ mDevice.waitForIdle();
+ }
try {
findDocument("Download").click();
@@ -486,8 +498,14 @@
// save button is enabled for Android folder
assertTrue(findSaveButton().isEnabled());
- findRoot(getDeviceName()).click();
- mDevice.waitForIdle();
+ try {
+ findRoot(getDeviceName()).click();
+ mDevice.waitForIdle();
+ } catch(UiObjectNotFoundException e) {
+ // It might be possible that OEMs customize storage root to "Internal storage".
+ findRoot(DEFAULT_DEVICE_NAME).click();
+ mDevice.waitForIdle();
+ }
try {
findDocument("Download").click();
@@ -804,8 +822,9 @@
mActivity.startActivityForResult(intent, REQUEST_CODE);
mDevice.waitForIdle();
- // assert the default root is internal storage root
- assertToolbarTitleEquals(getDeviceName());
+ // assert toolbar title should be set to either device name or "Internal storage".
+ assertTrue(checkToolbarTitleEquals(getDeviceName())
+ || checkToolbarTitleEquals(DEFAULT_DEVICE_NAME));
// no Downloads root
assertFalse(findRoot("Downloads").exists());
@@ -974,6 +993,9 @@
assertEquals(displayName, getColumn(uri, Document.COLUMN_DISPLAY_NAME));
assertEquals(mimeType, getColumn(uri, Document.COLUMN_MIME_TYPE));
+ // Multiple calls to this method too quickly may result in onActivityResult being skipped.
+ mDevice.waitForIdle();
+
return uri;
}
diff --git a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.bp b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.bp
index 5f80327..d667402 100644
--- a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.bp
@@ -30,3 +30,23 @@
"general-tests",
],
}
+
+android_test_import {
+ name: "CtsDuplicatePermissionDeclareApp_DifferentProtectionLevel",
+ apk: "apk/b211934395_DifferentProtectionLevel.apk",
+ presigned: true,
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_import {
+ name: "CtsDuplicatePermissionDeclareApp_SameProtectionLevel",
+ apk: "apk/b211934395_SameProtectionLevel.apk",
+ presigned: true,
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/apk/b211934395_DifferentProtectionLevel.apk b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/apk/b211934395_DifferentProtectionLevel.apk
new file mode 100644
index 0000000..e25d176
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/apk/b211934395_DifferentProtectionLevel.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/apk/b211934395_SameProtectionLevel.apk b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/apk/b211934395_SameProtectionLevel.apk
new file mode 100644
index 0000000..15e6a5d
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/apk/b211934395_SameProtectionLevel.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/Android.bp b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/Android.bp
index 07ec92a..74403bc 100644
--- a/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/Android.bp
@@ -57,6 +57,7 @@
android_test {
name: "CtsSignatureQueryServiceTest_v2",
defaults: ["cts_support_defaults"],
+ manifest: "AndroidManifest_v2.xml",
compile_multilib: "both",
sdk_version: "current",
srcs: ["src/**/*.java"],
@@ -75,7 +76,7 @@
],
certificate: ":ec-p256_2",
additional_certificates: [":ec-p256"],
- lineage : ":ec-p256-por_1_2-default-caps",
+ lineage: ":ec-p256-por_1_2-default-caps",
v4_signature: true,
// Disable dexpreopt and <uses-library> check for test
enforce_uses_libs: false,
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest.xml
index e4981ee..0436f08 100644
--- a/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest.xml
@@ -14,7 +14,8 @@
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.appsecurity.cts.keyrotationtest.test">
+ package="android.appsecurity.cts.keyrotationtest.test"
+ android:versionCode="1">
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="android.appsecurity.cts.keyrotationtest" />
<application/>
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest_v2.xml b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest_v2.xml
new file mode 100644
index 0000000..3206a52
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest_v2.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.appsecurity.cts.keyrotationtest.test"
+ android:versionCode="2">
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.appsecurity.cts.keyrotationtest" />
+ <application/>
+</manifest>
+
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
index 36ad932..b3d1d1c 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
@@ -16,9 +16,12 @@
package com.android.cts.mediastorageapp;
+import static android.provider.MediaStore.VOLUME_EXTERNAL;
+
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -70,6 +73,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.concurrent.Callable;
@@ -124,7 +129,7 @@
final HashSet<Long> seen = new HashSet<>();
try (Cursor c = mContentResolver.query(
- MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
+ MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
new String[] { MediaColumns._ID }, null, null)) {
while (c.moveToNext()) {
seen.add(c.getLong(0));
@@ -217,10 +222,191 @@
}
/**
- * Test prefix and non-prefix uri grant for all packages
+ * If the app grants read UriPermission to the uri without id (E.g.
+ * MediaStore.Audio.Media.EXTERNAL_CONTENT_URI), the query result of the uri should be the
+ * same without granting permission.
*/
@Test
- public void testGrantUriPermission() {
+ public void testReadUriPermissionOnUriWithoutId_sameQueryResult() throws Exception {
+ // For Audio, Image, Video, If the app doesn't have delete access to the uri,
+ // MediaProvider throws SecurityException to give callers interacting with a specific media
+ // item a chance to escalate access if they don't already have it. Check SecurityException
+ // for them.
+ doReadUriPermissionOnUriWithoutId_sameQueryResult(
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ MediaStorageTest::createAudio, /* checkExceptionForDelete= */ true);
+ doReadUriPermissionOnUriWithoutId_sameQueryResult(
+ MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+ MediaStorageTest::createVideo,/* checkExceptionForDelete= */ true);
+ doReadUriPermissionOnUriWithoutId_sameQueryResult(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ MediaStorageTest::createImage,/* checkExceptionForDelete= */ true);
+
+ doReadUriPermissionOnUriWithoutId_sameQueryResult(
+ MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
+ MediaStorageTest::createAudio, /* checkExceptionForDelete= */ true);
+ doReadUriPermissionOnUriWithoutId_sameQueryResult(
+ MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,
+ MediaStorageTest::createAudio, /* checkExceptionForDelete= */ true);
+ doReadUriPermissionOnUriWithoutId_sameQueryResult(
+ MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI,
+ MediaStorageTest::createAudio, /* checkExceptionForDelete= */ true);
+
+ doReadUriPermissionOnUriWithoutId_sameQueryResult(
+ MediaStore.Downloads.EXTERNAL_CONTENT_URI,
+ MediaStorageTest::createDownload,/* checkExceptionForDelete= */ false);
+ doReadUriPermissionOnUriWithoutId_sameQueryResult(
+ MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+ MediaStorageTest::createFile,/* checkExceptionForDelete= */ false);
+ doReadUriPermissionOnUriWithoutId_sameQueryResult(
+ MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+ MediaStorageTest::createPlaylist,/* checkExceptionForDelete= */ false);
+ }
+
+ /**
+ * If the app grants read UriPermission to the uri without id (E.g.
+ * MediaStore.Audio.Media.EXTERNAL_CONTENT_URI), the query result of the uri should be the
+ * same without granting permission.
+ */
+ private void doReadUriPermissionOnUriWithoutId_sameQueryResult(Uri collectionUri,
+ Callable<Uri> create, boolean checkExceptionForDelete) throws Exception {
+ final int flagGrantRead = Intent.FLAG_GRANT_READ_URI_PERMISSION;
+ final Uri red = create.call();
+ final Uri blue = create.call();
+ clearMediaOwner(blue, mUserId);
+ final int originalCount;
+
+ try {
+ try (Cursor c = mContentResolver.query(collectionUri, new String[]{MediaColumns._ID},
+ null, null)) {
+ originalCount = c.getCount();
+ }
+
+ mContext.grantUriPermission(mContext.getPackageName(), collectionUri, flagGrantRead);
+ try (Cursor c = mContentResolver.query(collectionUri, new String[]{MediaColumns._ID},
+ null, null)) {
+ assertWithMessage("After grant read UriPermission to " + collectionUri.toString()
+ + ", the item count of the query result" ).that(c.getCount()).isEqualTo(
+ originalCount);
+ }
+
+ try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "rw")) {
+ }
+
+ try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "r")) {
+ fail("Expected read access to " + blue.toString() + " be blocked");
+ } catch (SecurityException | FileNotFoundException expected) {
+ }
+
+ try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "w")) {
+ fail("Expected write access to " + blue.toString() + " be blocked");
+ } catch (SecurityException | FileNotFoundException expected) {
+ }
+
+ // If checkExceptionForDelete is true, throws SecurityException is as we expected.
+ // Otherwise, the app doesn't have delete access to the file, the deleted count is 0.
+ if (checkExceptionForDelete) {
+ try {
+ mContentResolver.delete(blue, null);
+ fail("Expected delete access to " + blue.toString() + " be blocked");
+ } catch (SecurityException expected) {
+ }
+ } else {
+ final int count = mContentResolver.delete(blue, null);
+ assertThat(count).isEqualTo(0);
+ }
+ } finally {
+ mContext.revokeUriPermission(mContext.getPackageName(), collectionUri, flagGrantRead);
+ }
+ }
+
+ /**
+ * b/197302116. The apps can't be granted prefix UriPermissions to the uri, when the query
+ * result of the uri is 1.
+ */
+ @Test
+ public void testOwningOneFileNotGrantPrefixUriPermission() throws Exception {
+ doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ MediaStorageTest::createAudio);
+ doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+ MediaStorageTest::createVideo);
+ doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ MediaStorageTest::createImage);
+ doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Downloads.EXTERNAL_CONTENT_URI,
+ MediaStorageTest::createDownload);
+ doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+ MediaStorageTest::createFile);
+ doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+ MediaStorageTest::createPlaylist);
+ }
+
+ /**
+ * The apps can't be granted prefix UriPermissions to the uri without id, when the query result
+ * of the uri is 1.
+ */
+ private void doOwningOneFileNotGrantPrefixUriPermission(Uri collectionUri, Callable<Uri> create)
+ throws Exception {
+
+ clearOwnFiles(collectionUri);
+
+ final Uri red = create.call();
+ final Uri blue = create.call();
+ clearMediaOwner(blue, mUserId);
+
+ try (Cursor c = mContentResolver.query(collectionUri, new String[]{MediaColumns._ID}, null,
+ null)) {
+ assertThat(c.getCount()).isEqualTo(1);
+ c.moveToFirst();
+ assertThat(c.getLong(0)).isEqualTo(ContentUris.parseId(red));
+ }
+
+ final int flagGrantReadPrefix =
+ Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+ try {
+ mContext.grantUriPermission(mContext.getPackageName(), collectionUri,
+ flagGrantReadPrefix);
+ fail("Expected granting to " + collectionUri.toString() + " be blocked for flag 0x"
+ + Integer.toHexString(flagGrantReadPrefix));
+ } catch (SecurityException expected) {
+ }
+
+ try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "r")) {
+ }
+
+ try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "r")) {
+ fail("Expected read access to " + blue.toString() + " be blocked");
+ } catch (SecurityException | FileNotFoundException expected) {
+ }
+
+ final int flagGrantWritePrefix = Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+ try {
+ mContext.grantUriPermission(mContext.getPackageName(), collectionUri,
+ flagGrantWritePrefix);
+ fail("Expected granting to " + collectionUri.toString() + " be blocked for flag 0x"
+ + Integer.toHexString(flagGrantWritePrefix));
+ } catch (SecurityException expected) {
+ }
+
+ try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "rw")) {
+ }
+
+ try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "w")) {
+ fail("Expected write access to " + blue.toString() + " be blocked");
+ } catch (SecurityException | FileNotFoundException expected) {
+ }
+ }
+
+ @Test
+ public void testGrantUriPermission() throws Exception {
+ doGrantUriPermission_nonPrefixAndPrefix();
+ doGrantUriPermission_prefix();
+ }
+
+ /**
+ * Test prefix and non-prefix uri grant for all packages
+ */
+ private void doGrantUriPermission_nonPrefixAndPrefix() {
final int flagGrantRead = Intent.FLAG_GRANT_READ_URI_PERMISSION;
final int flagGrantWrite = Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
final int flagGrantReadPrefix =
@@ -228,20 +414,44 @@
final int flagGrantWritePrefix =
Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
- for (Uri uri : new Uri[] {
+ for (Uri uri : new Uri[]{
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
- MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
+ MediaStore.Files.getContentUri(VOLUME_EXTERNAL)
}) {
// Non-prefix grant
- checkGrantUriPermission(uri, flagGrantRead, true);
- checkGrantUriPermission(uri, flagGrantWrite, true);
+ checkGrantUriPermission(uri, flagGrantRead, /* isGrantAllowed */ true);
+ checkGrantUriPermission(uri, flagGrantWrite, /* isGrantAllowed */ true);
// Prefix grant
- checkGrantUriPermission(uri, flagGrantReadPrefix, false);
- checkGrantUriPermission(uri, flagGrantWritePrefix, false);
+ checkGrantUriPermission(uri, flagGrantReadPrefix, /* isGrantAllowed */ false);
+ checkGrantUriPermission(uri, flagGrantWritePrefix, /* isGrantAllowed */ false);
+
+ // revoke granted permissions
+ mContext.revokeUriPermission(uri, flagGrantRead | flagGrantWrite);
+ }
+ }
+
+ /**
+ * b/194539422. Test prefix uri grant for all packages
+ */
+ private void doGrantUriPermission_prefix() {
+ final int flagGrantReadPrefix =
+ Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+ final int flagGrantWritePrefix =
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+
+ for (Uri uri : new Uri[]{
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ MediaStore.Downloads.EXTERNAL_CONTENT_URI,
+ MediaStore.Files.getContentUri(VOLUME_EXTERNAL)
+ }) {
+ checkGrantUriPermission(uri, flagGrantReadPrefix, /* isGrantAllowed */ false);
+ checkGrantUriPermission(uri, flagGrantWritePrefix, /* isGrantAllowed */ false);
}
}
@@ -251,7 +461,8 @@
} else {
try {
mContext.grantUriPermission(mContext.getPackageName(), uri, mode);
- fail("Expected granting to be blocked for flag 0x" + Integer.toHexString(mode));
+ fail("Expected granting to " + uri.toString() + " be blocked for flag 0x"
+ + Integer.toHexString(mode));
} catch (SecurityException expected) {
}
}
@@ -438,15 +649,14 @@
try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "w")) {
}
- // Wait for MediaStore to be idle to avoid flakiness due to race conditions between
- // MediaStore.scanFile (which is called above in #openFileDescriptor) and rename (which is
- // called below). This is a known issue: b/158982091
+ // Wait for MediaStore to be idle to avoid flakiness due to race conditions
MediaStore.waitForIdle(mContentResolver);
// Check File API support
assertAccessFileAPISupport(file);
assertReadWriteFileAPISupport(file);
assertRenameFileAPISupport(file);
+ assertRenameAndReplaceFileAPISupport(file, create);
assertDeleteFileAPISupport(file);
}
@@ -471,15 +681,52 @@
public void assertRenameFileAPISupport(File oldFile) throws Exception {
final String oldName = oldFile.getAbsolutePath();
final String extension = oldName.substring(oldName.lastIndexOf('.')).trim();
- // TODO(b/178816495): Changing the extension changes the media-type and hence the media-URI
- // corresponding to the new file is not accessible to the caller. Rename to the same
- // extension so that the test app does not lose access and is able to delete the file.
- final String newName = "cts" + System.nanoTime() + extension;
- final File newFile = Environment.buildPath(Environment.getExternalStorageDirectory(),
- Environment.DIRECTORY_DOWNLOADS, newName);
- assertThat(oldFile.renameTo(newFile)).isTrue();
+ // Rename to same extension so test app does not lose access to file.
+ final String newRelativeName = "cts" + System.nanoTime() + extension;
+ final File newFile = Environment.buildPath(
+ Environment.getExternalStorageDirectory(),
+ Environment.DIRECTORY_DOWNLOADS,
+ newRelativeName);
+ final String newName = newFile.getAbsolutePath();
+ assertWithMessage("Rename from oldName [%s] to newName [%s]", oldName, newName)
+ .that(oldFile.renameTo(newFile))
+ .isTrue();
// Rename back to oldFile for other ops like delete
- assertThat(newFile.renameTo(oldFile)).isTrue();
+ assertWithMessage("Rename back from newName [%s] to oldName [%s]", newName, oldName)
+ .that(newFile.renameTo(oldFile))
+ .isTrue();
+ }
+
+ public void assertRenameAndReplaceFileAPISupport(File oldFile, Callable<Uri> create)
+ throws Exception {
+ final String oldName = oldFile.getAbsolutePath();
+
+ // Create new file to which we do not have any access.
+ final Uri newUri = create.call();
+ assertWithMessage("Check newFile created").that(newUri).isNotNull();
+ File newFile = new File(queryForSingleColumn(newUri, MediaColumns.DATA));
+ final String newName = newFile.getAbsolutePath();
+ clearMediaOwner(newUri, mUserId);
+
+ assertWithMessage(
+ "Rename should fail without newFile grant from oldName [%s] to newName [%s]",
+ oldName, newName)
+ .that(oldFile.renameTo(newFile))
+ .isFalse();
+
+ // Grant access to newFile and rename should succeed.
+ doEscalation(MediaStore.createWriteRequest(mContentResolver, Arrays.asList(newUri)));
+ assertWithMessage("Rename from oldName [%s] to newName [%s]", oldName, newName)
+ .that(oldFile.renameTo(newFile))
+ .isTrue();
+
+ // We need to request grant on newUri again, since the rename above caused the URI grant
+ // to be revoked.
+ doEscalation(MediaStore.createWriteRequest(mContentResolver, Arrays.asList(newUri)));
+ // Rename back to oldFile for other ops like delete
+ assertWithMessage("Rename back from newName [%s] to oldName [%s]", newName, oldName)
+ .that(newFile.renameTo(oldFile))
+ .isTrue();
}
private void assertDeleteFileAPISupport(File file) throws Exception {
@@ -783,12 +1030,38 @@
}
}
+ private static Uri createDownload() throws IOException {
+ final String content = "<html><body>Content</body></html>";
+ final String displayName = "cts" + System.nanoTime();
+ final String mimeType = "text/html";
+ final Context context = InstrumentationRegistry.getTargetContext();
+ final PendingParams params = new PendingParams(
+ MediaStore.Downloads.EXTERNAL_CONTENT_URI, displayName, mimeType);
+
+ final Uri pendingUri = MediaStoreUtils.createPending(context, params);
+ assertNotNull(pendingUri);
+ try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) {
+ try (PrintWriter pw = new PrintWriter(session.openOutputStream())) {
+ pw.print(content);
+ }
+ try (OutputStream out = session.openOutputStream()) {
+ out.write(content.getBytes(StandardCharsets.UTF_8));
+ }
+ return session.publish();
+ }
+ }
+
+ private static Uri createFile() throws IOException {
+ return createSubtitle();
+ }
+
private static Uri createAudio() throws IOException {
final Context context = InstrumentationRegistry.getTargetContext();
final String displayName = "cts" + System.nanoTime();
final PendingParams params = new PendingParams(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, displayName, "audio/mpeg");
final Uri pendingUri = MediaStoreUtils.createPending(context, params);
+
try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) {
try (InputStream in = context.getResources().getAssets().open("testmp3.mp3");
OutputStream out = session.openOutputStream()) {
@@ -843,7 +1116,7 @@
final Context context = InstrumentationRegistry.getTargetContext();
final String displayName = "cts" + System.nanoTime();
final PendingParams params = new PendingParams(
- MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), displayName,
+ MediaStore.Files.getContentUri(VOLUME_EXTERNAL), displayName,
"application/x-subrip");
final Uri pendingUri = MediaStoreUtils.createPending(context, params);
try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) {
@@ -865,6 +1138,18 @@
}
}
+ private static void clearOwnFiles(Uri uri) throws Exception {
+ final ContentResolver resolver = InstrumentationRegistry.getTargetContext()
+ .getContentResolver();
+ try (Cursor c = resolver.query(uri, new String[]{MediaColumns._ID}, null, null)) {
+ while(c.moveToNext()) {
+ final long id = c.getLong(0);
+ final Uri contentUri = ContentUris.withAppendedId(uri, id);
+ resolver.delete(contentUri, null);
+ }
+ }
+ }
+
private static void clearMediaOwner(Uri uri, int userId) throws IOException {
final String cmd = String.format(
"content update --uri %s --user %d --bind owner_package_name:n:",
diff --git a/hostsidetests/appsecurity/test-apps/NoDataStorageApp/Android.bp b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/Android.bp
new file mode 100644
index 0000000..146524c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsNoAppDataStorageApp",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "test_current",
+ static_libs: [
+ "androidx.test.runner",
+ "androidx.test.core",
+ "truth-prebuilt",
+ ],
+ srcs: ["src/**/*.java"],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/NoDataStorageApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/AndroidManifest.xml
new file mode 100644
index 0000000..ea164a1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.noappstorage">
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.noappstorage"
+ android:label="Tests for app without data storage" />
+
+ <application>
+ <property android:name="android.internal.PROPERTY_NO_APP_DATA_STORAGE"
+ android:value="true" />
+ <uses-library android:name="android.test.runner"/>
+ </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/NoDataStorageApp/OWNERS b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/OWNERS
new file mode 100644
index 0000000..601d04b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/OWNERS
@@ -0,0 +1,2 @@
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
+ioffe@google.com
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/NoDataStorageApp/src/com/android/cts/noappstorage/NoAppDataStorageTest.java b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/src/com/android/cts/noappstorage/NoAppDataStorageTest.java
new file mode 100644
index 0000000..e69ef3e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/src/com/android/cts/noappstorage/NoAppDataStorageTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.noappstorage;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+
+/**
+ * Tests that exercise behaviour of an app without access to apps' data storage.
+ */
+// TODO(b/211761016): add tests for external storage.
+@RunWith(JUnit4.class)
+public class NoAppDataStorageTest {
+
+ private final Context mCeContext = getInstrumentation().getContext();
+ private final Context mDeContext = mCeContext.createDeviceProtectedStorageContext();
+
+ @Test
+ public void testNoInternalCeStorage() throws Exception {
+ assertDirDoesNotExist(mCeContext.getDataDir());
+ assertDirDoesNotExist(mCeContext.getFilesDir());
+ assertDirDoesNotExist(mCeContext.getCacheDir());
+ assertDirDoesNotExist(mCeContext.getCodeCacheDir());
+ }
+
+ @Test
+ public void testNoInternalDeStorage() throws Exception {
+ assertDirDoesNotExist(mDeContext.getDataDir());
+ assertDirDoesNotExist(mDeContext.getFilesDir());
+ assertDirDoesNotExist(mDeContext.getCacheDir());
+ assertDirDoesNotExist(mDeContext.getCodeCacheDir());
+ }
+
+ private void assertDirDoesNotExist(File dir) throws Exception {
+ assertThat(dir.exists()).isFalse();
+ assertThat(dir.mkdirs()).isFalse();
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/Android.bp b/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/Android.bp
index f375fa5..0af7515 100644
--- a/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/Android.bp
@@ -35,6 +35,7 @@
"general-tests",
],
manifest: "AndroidManifest.xml",
+ sdk_version: "current",
}
android_test_helper_app {
@@ -56,6 +57,7 @@
"general-tests",
],
manifest: "AndroidManifestTestOnly.xml",
+ sdk_version: "current",
}
android_test_helper_app {
@@ -77,6 +79,7 @@
"general-tests",
],
manifest: "AndroidManifestTargetQ.xml",
+ sdk_version: "current",
}
android_test_helper_app {
@@ -98,6 +101,7 @@
"general-tests",
],
manifest: "AndroidManifestTargetR.xml",
+ sdk_version: "current",
}
android_test_helper_app {
@@ -119,4 +123,5 @@
"general-tests",
],
manifest: "AndroidManifestTargetS.xml",
-}
\ No newline at end of file
+ sdk_version: "current",
+}
diff --git a/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/src/com/android/cts/readsettingsfieldsapp/ReadSettingsFieldsTest.java b/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/src/com/android/cts/readsettingsfieldsapp/ReadSettingsFieldsTest.java
index e6e2ced..fdb701d 100644
--- a/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/src/com/android/cts/readsettingsfieldsapp/ReadSettingsFieldsTest.java
+++ b/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/src/com/android/cts/readsettingsfieldsapp/ReadSettingsFieldsTest.java
@@ -27,7 +27,6 @@
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.util.Arrays;
import java.util.ArrayList;
public class ReadSettingsFieldsTest extends AndroidTestCase {
@@ -53,7 +52,7 @@
if (isSettingsDeprecated(ex)) {
continue;
}
- fail("Reading public " + settingsClass.getSimpleName() + " settings key <" + key
+ fail("Reading non-hidden " + settingsClass.getSimpleName() + " settings key <" + key
+ "> should not raise exception! "
+ "Did you forget to add @Readable annotation?\n" + ex.getMessage());
}
@@ -220,6 +219,29 @@
hiddenSettingsKeys);
}
+
+ public void testSettingsKeysNotReadableForAfterR() {
+ final String keyWithTargetSdkR = "media_button_receiver";
+ try {
+ // Verify that the hidden key is not readable because of maxTargetSdk restriction
+ callGetStringMethod(Settings.System.class, keyWithTargetSdkR);
+ fail("Reading hidden settings key <" + keyWithTargetSdkR
+ + "> should raise!");
+ } catch (SecurityException ex) {
+ assertTrue(ex.getMessage().contains("targetSdkVersion"));
+ }
+ }
+
+ public void testSettingsKeysReadableForRMinus() {
+ final String keyWithTargetSdkR = "media_button_receiver";
+ try {
+ // Verify that the hidden key can still be read
+ callGetStringMethod(Settings.System.class, keyWithTargetSdkR);
+ } catch (SecurityException ex) {
+ fail("Reading hidden settings key <" + keyWithTargetSdkR + "> should not raise!");
+ }
+ }
+
public void testQueryGlobalSettingsNoHiddenKeysWithoutAnnotation() {
checkQueryResults(Settings.Global.CONTENT_URI, Settings.Global.class);
}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
index c42fd09..749de46 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
@@ -67,35 +67,6 @@
],
}
-android_test_helper_app {
- name: "CtsSplitApp29",
- defaults: ["CtsSplitAppDefaults"],
- package_splits: [
- "mdpi-v4",
- "hdpi-v4",
- "xhdpi-v4",
- "xxhdpi-v4",
- "v7",
- "v23",
- "fr",
- "de",
- ],
- certificate: ":cts-testkey1",
- aaptflags: [
- "--version-code 100",
- "--version-name OneHundred",
- "--replace-version",
- ],
- // Feature splits are dependent on this base, so it must be exported.
- export_package_resources: true,
- test_suites: [
- "cts",
- "general-tests",
- "mts-mainline-infra",
- ],
- target_sdk_version: "29"
-}
-
// Define a variant with a different revision code
android_test_helper_app {
name: "CtsSplitAppDiffRevision",
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/Android.bp
new file mode 100644
index 0000000..e8656a1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/Android.bp
@@ -0,0 +1,76 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Define a variant requiring two split types for install
+android_test_helper_app {
+ name: "CtsRequiredSplitTypeSplitApp",
+ manifest: "AndroidManifest.xml",
+ sdk_version: "current",
+ min_sdk_version: "4",
+ aapt_include_all_resources: true,
+ certificate: ":cts-testkey1",
+ aaptflags: [
+ "--version-code 100",
+ "--version-name OneHundredRevisionTwelve",
+ "--replace-version",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+// Define a variant requiring three split types for install
+android_test_helper_app {
+ name: "CtsRequiredSplitTypeSplitAppUpdated",
+ manifest: "AndroidManifest_updated.xml",
+ sdk_version: "current",
+ min_sdk_version: "4",
+ aapt_include_all_resources: true,
+ certificate: ":cts-testkey1",
+ aaptflags: [
+ "--version-code 100",
+ "--revision-code 10",
+ "--version-name OneHundredRevisionTen",
+ "--replace-version",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+// Define a variant having invalid required split types
+android_test_helper_app {
+ name: "CtsInvalidRequiredSplitTypeSplitApp",
+ manifest: "AndroidManifest_bad.xml",
+ sdk_version: "current",
+ min_sdk_version: "4",
+ aapt_include_all_resources: true,
+ certificate: ":cts-testkey1",
+ aaptflags: [
+ "--version-code 100",
+ "--revision-code 10",
+ "--version-name OneHundredRevisionTen",
+ "--replace-version",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest.xml
new file mode 100644
index 0000000..907691c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.splitapp"
+ android:targetSandboxVersion="2"
+ android:requiredSplitTypes="density,locale">
+
+ <uses-sdk android:minSdkVersion="4"
+ android:targetSdkVersion="27"/>
+
+ <application android:label="SplitApp">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.splitapp"/>
+
+</manifest>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest_bad.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest_bad.xml
new file mode 100644
index 0000000..3e7a1b4
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest_bad.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.splitapp"
+ android:targetSandboxVersion="2"
+ android:requiredSplitTypes="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789">
+
+ <uses-sdk android:minSdkVersion="4"
+ android:targetSdkVersion="27"/>
+
+ <application android:label="SplitApp">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.splitapp"/>
+
+</manifest>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest_updated.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest_updated.xml
new file mode 100644
index 0000000..cdff5b5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest_updated.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.splitapp"
+ android:targetSandboxVersion="2"
+ android:requiredSplitTypes="density,locale,feature">
+
+ <uses-sdk android:minSdkVersion="4"
+ android:targetSdkVersion="27"/>
+
+ <application android:label="SplitApp">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.splitapp"/>
+
+</manifest>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/Android.bp
new file mode 100644
index 0000000..ac1fbe8
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/Android.bp
@@ -0,0 +1,101 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsSplitAppTypeDensity",
+ manifest: "density/AndroidManifest.xml",
+ certificate: ":cts-testkey1",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsSplitAppTypeLocale",
+ manifest: "locale/AndroidManifest.xml",
+ certificate: ":cts-testkey1",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsSplitAppTypeMultiple",
+ manifest: "multitype/AndroidManifest.xml",
+ certificate: ":cts-testkey1",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsSplitAppTypeFoo",
+ manifest: "foo/AndroidManifest.xml",
+ certificate: ":cts-testkey1",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsSplitAppTypeFeature",
+ manifest: "feature/AndroidManifest.xml",
+ certificate: ":cts-testkey1",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsSplitAppTypeFeatureData",
+ manifest: "feature_data/AndroidManifest.xml",
+ certificate: ":cts-testkey1",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsSplitAppTypeFeatureFoo",
+ manifest: "feature_foo/AndroidManifest.xml",
+ certificate: ":cts-testkey1",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/density/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/density/AndroidManifest.xml
new file mode 100644
index 0000000..9e50b38
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/density/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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"
+ android:versionCode="100"
+ package="com.android.cts.splitapp"
+ split="config.xxhdpi"
+ targetConfig="xxhdpi"
+ android:splitTypes="density">
+
+ <application
+ android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature/AndroidManifest.xml
new file mode 100644
index 0000000..d681953
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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"
+ android:versionCode="100"
+ package="com.android.cts.splitapp"
+ android:isFeatureSplit="true"
+ split="feature_foo"
+ android:splitTypes="feature"
+ android:requiredSplitTypes="feature.data">
+
+ <application
+ android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature_data/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature_data/AndroidManifest.xml
new file mode 100644
index 0000000..496ef5f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature_data/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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"
+ android:versionCode="100"
+ package="com.android.cts.splitapp"
+ split="feature_foo.data"
+ android:splitTypes="feature.data">
+
+ <application
+ android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature_foo/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature_foo/AndroidManifest.xml
new file mode 100644
index 0000000..80f06b0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature_foo/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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"
+ android:versionCode="100"
+ package="com.android.cts.splitapp"
+ split="feature_foo.foo"
+ android:splitTypes="feature.foo">
+
+ <application
+ android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/foo/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/foo/AndroidManifest.xml
new file mode 100644
index 0000000..e514d9b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/foo/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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"
+ android:versionCode="100"
+ package="com.android.cts.splitapp"
+ split="config.foo"
+ android:splitTypes="foo">
+
+ <application
+ android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/locale/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/locale/AndroidManifest.xml
new file mode 100644
index 0000000..372bbb0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/locale/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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"
+ android:versionCode="100"
+ package="com.android.cts.splitapp"
+ split="config.de"
+ android:splitTypes="locale">
+
+ <application
+ android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/multitype/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/multitype/AndroidManifest.xml
new file mode 100644
index 0000000..d640edb
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/multitype/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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"
+ android:versionCode="100"
+ package="com.android.cts.splitapp"
+ split="config.split"
+ android:splitTypes="locale,density">
+
+ <application
+ android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/src/android/appsecurity/cts/v3rotationtests/V3RotationTest.java b/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/src/android/appsecurity/cts/v3rotationtests/V3RotationTest.java
index 2f06395..d617251 100644
--- a/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/src/android/appsecurity/cts/v3rotationtests/V3RotationTest.java
+++ b/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/src/android/appsecurity/cts/v3rotationtests/V3RotationTest.java
@@ -251,6 +251,36 @@
pm.hasSigningCertificate(uid, secondCertBytes, PackageManager.CERT_INPUT_SHA256));
}
+ public void testUsingOriginalSigner() throws Exception {
+ // Verifies the platform only recognized the original signing key during installation.
+ PackageManager pm = getContext().getPackageManager();
+ PackageInfo pi = pm.getPackageInfo(PKG, PackageManager.GET_SIGNING_CERTIFICATES);
+ assertFalse(
+ "APK is expected to use the original signing key, but past signing certificates "
+ + "were reported",
+ pi.signingInfo.hasPastSigningCertificates());
+ assertExpectedSignatures(pi.signingInfo.getApkContentsSigners(), EC_P256_FIRST_CERT_HEX);
+ byte[] secondCertBytes = fromHexToByteArray(EC_P256_SECOND_CERT_HEX);
+ assertFalse("APK is not expected to have the rotated key in its signing lineage",
+ pm.hasSigningCertificate(PKG, secondCertBytes, PackageManager.CERT_INPUT_RAW_X509));
+ }
+
+ public void testUsingRotatedSigner() throws Exception {
+ // Verifies the platform recognized the rotated signing key during installation.
+ PackageManager pm = getContext().getPackageManager();
+ PackageInfo pi = pm.getPackageInfo(PKG, PackageManager.GET_SIGNING_CERTIFICATES);
+ assertTrue(
+ "APK is expected to be signed with the rotated signing key, but past signing "
+ + "certificates were not reported",
+ pi.signingInfo.hasPastSigningCertificates());
+ assertExpectedSignatures(pi.signingInfo.getApkContentsSigners(), EC_P256_SECOND_CERT_HEX);
+ assertExpectedSignatures(pi.signingInfo.getSigningCertificateHistory(),
+ EC_P256_FIRST_CERT_HEX, EC_P256_SECOND_CERT_HEX);
+ byte[] firstCertBytes = fromHexToByteArray(EC_P256_FIRST_CERT_HEX);
+ assertTrue("APK is expected to have the original key in its signing lineage",
+ pm.hasSigningCertificate(PKG, firstCertBytes, PackageManager.CERT_INPUT_RAW_X509));
+ }
+
private static byte[] fromHexToByteArray(String str) {
if (str == null || str.length() == 0 || str.length() % 2 != 0) {
return null;
diff --git a/hostsidetests/appsecurity/test-apps/rro/OWNERS b/hostsidetests/appsecurity/test-apps/rro/OWNERS
index 21cd9d9..870d1a4 100644
--- a/hostsidetests/appsecurity/test-apps/rro/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/rro/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 568761
-rtmitchell@google.com
+zyy@google.com
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/Android.bp b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/Android.bp
index fbc77e1..607f13f 100644
--- a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/Android.bp
@@ -21,8 +21,16 @@
android_test_helper_app {
name: "CtsOverlayTarget",
defaults: ["cts_support_defaults"],
- sdk_version: "current",
+ sdk_version: "test_current",
certificate: ":cts-testkey1",
+ static_libs: [
+ "truth-prebuilt",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ],
resource_dirs: [
"res",
"res_overlayable",
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/AndroidManifest.xml
index a0d609a..03e4f7d 100644
--- a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/AndroidManifest.xml
@@ -16,4 +16,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.overlay.target">
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name=".OverlayTargetActivity" android:exported="false"
+ android:configChanges="@integer/config_changes_assets_paths" />
+ <service android:name=".OverlayTargetService" android:exported="false" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.overlay.target" />
</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/res/values/integer.xml b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/res/values/integer.xml
new file mode 100644
index 0000000..67c0149
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/res/values/integer.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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>
+ <!-- The integer to indicate that activity handles assets paths changes itself -->
+ <integer name="config_changes_assets_paths">0x80000000</integer>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetActivity.java b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetActivity.java
new file mode 100644
index 0000000..d94d511
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetActivity.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.overlay.target;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.function.BiConsumer;
+
+/**
+ * A test activity to verify that the assets paths configuration changes are received if the
+ * overlay targeting state is changed.
+ */
+public class OverlayTargetActivity extends Activity {
+ private BiConsumer<OverlayTargetActivity, Configuration> mConfigurationChangedCallback;
+
+ /**
+ * A boolean value to determine whether a stub service can be started when the activity
+ * is launched.
+ */
+ public static final String EXTRA_START_SERVICE =
+ "com.android.cts.overlay.intent.extra.START_SERVICE";
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ keepScreenOn();
+ if (savedInstanceState == null) {
+ startServiceIfNecessary(getIntent());
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ final BiConsumer<OverlayTargetActivity, Configuration> callback =
+ mConfigurationChangedCallback;
+ if (callback != null) {
+ callback.accept(this, newConfig);
+ }
+ }
+
+ /** Registers the callback of onConfigurationChanged. */
+ public void setConfigurationChangedCallback(
+ BiConsumer<OverlayTargetActivity, Configuration> callbacks) {
+ mConfigurationChangedCallback = callbacks;
+ }
+
+ private void keepScreenOn() {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ setTurnScreenOn(true);
+ KeyguardManager km = getSystemService(KeyguardManager.class);
+ if (km != null) {
+ km.requestDismissKeyguard(this, null);
+ }
+ }
+
+ private void startServiceIfNecessary(Intent intent) {
+ if (intent == null) {
+ return;
+ }
+ final boolean startService = intent.getBooleanExtra(EXTRA_START_SERVICE, false);
+ if (!startService) {
+ return;
+ }
+ final Intent serviceIntent = new Intent(this, OverlayTargetService.class);
+ startService(serviceIntent);
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetService.java b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetService.java
new file mode 100644
index 0000000..3fb4fa0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetService.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.overlay.target;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+/** A stub service used by {@link OverlayTargetActivity} */
+public class OverlayTargetService extends Service {
+ private Binder mBinder = new Binder();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetTest.java b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetTest.java
new file mode 100644
index 0000000..938f1c4
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.overlay.target;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class OverlayTargetTest {
+ // overlay package
+ private static final String OVERLAY_ALL_PACKAGE_NAME = "com.android.cts.overlay.all";
+
+ // Overlay states
+ private static final String STATE_DISABLED = "STATE_DISABLED";
+ private static final String STATE_ENABLED = "STATE_ENABLED";
+
+ // Default timeout value
+ private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
+
+ // Keys for test arguments
+ private static final String PARAM_START_SERVICE = "start_service";
+
+ private Instrumentation mInstrumentation;
+
+ @Rule
+ public ActivityTestRule<OverlayTargetActivity> mActivityTestRule = new ActivityTestRule<>(
+ OverlayTargetActivity.class, false /* initialTouchMode */, false /* launchActivity */);
+
+ @Before
+ public void setup() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ launchOverlayTargetActivity(InstrumentationRegistry.getArguments());
+ assertThat(mActivityTestRule.getActivity()).isNotNull();
+ }
+
+ @Test
+ public void overlayEnabled_activityInForeground() throws Exception {
+ final OverlayTargetActivity targetActivity = mActivityTestRule.getActivity();
+ final CountDownLatch latch = new CountDownLatch(1);
+ targetActivity.setConfigurationChangedCallback((activity, config) -> {
+ latch.countDown();
+ activity.setConfigurationChangedCallback(null);
+ });
+
+ setOverlayEnabled(OVERLAY_ALL_PACKAGE_NAME, true /* enabled */);
+
+ if (!latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Fail to wait configuration changes for the overlay target activity.");
+ }
+ }
+
+ @Test
+ public void overlayEnabled_activityInBackground_toForeground() throws Exception {
+ final OverlayTargetActivity targetActivity = mActivityTestRule.getActivity();
+ // Activity goes into background
+ launchHome();
+ mInstrumentation.waitForIdleSync();
+ final CountDownLatch latch = new CountDownLatch(1);
+ targetActivity.setConfigurationChangedCallback((activity, config) -> {
+ latch.countDown();
+ activity.setConfigurationChangedCallback(null);
+ });
+ setOverlayEnabled(OVERLAY_ALL_PACKAGE_NAME, true /* enabled */);
+
+ if (latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Activity in background should not receive configuration changes");
+ }
+
+ // Bring activity to foreground
+ final Intent intent = new Intent(mInstrumentation.getTargetContext(),
+ OverlayTargetActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ targetActivity.startActivity(intent);
+
+ if (!latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Fail to wait configuration changes for the overlay target activity.");
+ }
+ }
+
+ private void launchOverlayTargetActivity(Bundle testArgs) {
+ final Intent intent = new Intent(mInstrumentation.getTargetContext(),
+ OverlayTargetActivity.class);
+ final boolean startService = (testArgs != null
+ && "true".equalsIgnoreCase(testArgs.getString(PARAM_START_SERVICE)));
+ intent.putExtra(OverlayTargetActivity.EXTRA_START_SERVICE, startService);
+ mActivityTestRule.launchActivity(intent);
+ mInstrumentation.waitForIdleSync();
+ }
+
+ private static void setOverlayEnabled(String overlayPackage, boolean enabled)
+ throws Exception {
+ final String current = getStateForOverlay(overlayPackage);
+ final String expected = enabled ? STATE_ENABLED : STATE_DISABLED;
+ assertThat(current).isNotEqualTo(expected);
+ SystemUtil.runShellCommand("cmd overlay "
+ + (enabled ? "enable" : "disable")
+ + " --user current "
+ + overlayPackage);
+ PollingCheck.check("Fail to wait overlay enabled state " + expected
+ + " for " + overlayPackage, TIMEOUT_MS,
+ () -> expected.equals(getStateForOverlay(overlayPackage)));
+ }
+
+ private static void launchHome() {
+ SystemUtil.runShellCommand("am start -W -a android.intent.action.MAIN"
+ + " -c android.intent.category.HOME");
+ }
+
+ private static String getStateForOverlay(String overlayPackage) {
+ final String errorMsg = "Fail to parse the state of overlay package " + overlayPackage;
+ final String result = SystemUtil.runShellCommand("cmd overlay dump");
+ final String overlayPackageForCurrentUser = overlayPackage + ":" + UserHandle.myUserId();
+ final int startIndex = result.indexOf(overlayPackageForCurrentUser);
+ assertWithMessage(errorMsg).that(startIndex).isAtLeast(0);
+
+ final int endIndex = result.indexOf('}', startIndex);
+ assertWithMessage(errorMsg).that(endIndex).isGreaterThan(startIndex);
+
+ final int stateIndex = result.indexOf("mState", startIndex);
+ assertWithMessage(errorMsg).that(startIndex).isLessThan(stateIndex);
+ assertWithMessage(errorMsg).that(stateIndex).isLessThan(endIndex);
+
+ final int colonIndex = result.indexOf(':', stateIndex);
+ assertWithMessage(errorMsg).that(stateIndex).isLessThan(colonIndex);
+ assertWithMessage(errorMsg).that(colonIndex).isLessThan(endIndex);
+
+ final int endLineIndex = result.indexOf('\n', colonIndex);
+ assertWithMessage(errorMsg).that(colonIndex).isLessThan(endLineIndex);
+ assertWithMessage(errorMsg).that(endLineIndex).isLessThan(endIndex);
+
+ return result.substring(colonIndex + 2, endLineIndex);
+ }
+}
diff --git a/hostsidetests/backup/Android.bp b/hostsidetests/backup/Android.bp
index c60681c..b75c8a2 100644
--- a/hostsidetests/backup/Android.bp
+++ b/hostsidetests/backup/Android.bp
@@ -21,7 +21,6 @@
srcs: ["src/**/*.java"],
// tag this module as a cts test artifact
test_suites: [
- "arcts",
"cts",
"general-tests",
"mts",
diff --git a/hostsidetests/backup/AutoRestoreApp/Android.bp b/hostsidetests/backup/AutoRestoreApp/Android.bp
index 58b58dd..fbab3cb 100644
--- a/hostsidetests/backup/AutoRestoreApp/Android.bp
+++ b/hostsidetests/backup/AutoRestoreApp/Android.bp
@@ -29,7 +29,6 @@
srcs: ["src/**/*.java"],
// tag this module as a cts test artifact
test_suites: [
- "arcts",
"cts",
"general-tests",
],
diff --git a/hostsidetests/backup/BackupTransportApp/Android.bp b/hostsidetests/backup/BackupTransportApp/Android.bp
index 7b07cbe..7a4484f 100644
--- a/hostsidetests/backup/BackupTransportApp/Android.bp
+++ b/hostsidetests/backup/BackupTransportApp/Android.bp
@@ -28,7 +28,6 @@
srcs: ["src/**/*.java"],
// tag this module as a cts test artifact
test_suites: [
- "arcts",
"cts",
"general-tests",
],
diff --git a/hostsidetests/backup/fullbackupapp/Android.bp b/hostsidetests/backup/fullbackupapp/Android.bp
index a7d81d6..ec5e274 100644
--- a/hostsidetests/backup/fullbackupapp/Android.bp
+++ b/hostsidetests/backup/fullbackupapp/Android.bp
@@ -26,7 +26,6 @@
srcs: ["src/**/*.java"],
// tag this module as a cts test artifact
test_suites: [
- "arcts",
"cts",
"general-tests",
],
diff --git a/hostsidetests/backup/includeexcludeapp/DataExtractionRulesApp/Android.bp b/hostsidetests/backup/includeexcludeapp/DataExtractionRulesApp/Android.bp
index 6a4a9b3..ae56169 100644
--- a/hostsidetests/backup/includeexcludeapp/DataExtractionRulesApp/Android.bp
+++ b/hostsidetests/backup/includeexcludeapp/DataExtractionRulesApp/Android.bp
@@ -27,7 +27,6 @@
srcs: ["src/**/*.java"],
// tag this module as a cts test artifact
test_suites: [
- "arcts",
"cts",
"general-tests",
],
diff --git a/hostsidetests/backup/includeexcludeapp/DataExtractionRulesApplicabilityApp/Android.bp b/hostsidetests/backup/includeexcludeapp/DataExtractionRulesApplicabilityApp/Android.bp
index e2ae477..9175932 100644
--- a/hostsidetests/backup/includeexcludeapp/DataExtractionRulesApplicabilityApp/Android.bp
+++ b/hostsidetests/backup/includeexcludeapp/DataExtractionRulesApplicabilityApp/Android.bp
@@ -27,7 +27,6 @@
srcs: ["src/**/*.java"],
// tag this module as a cts test artifact
test_suites: [
- "arcts",
"cts",
"general-tests",
],
diff --git a/hostsidetests/backup/includeexcludeapp/EncryptionAttributeApp/Android.bp b/hostsidetests/backup/includeexcludeapp/EncryptionAttributeApp/Android.bp
index fd106e9..9cfb39a 100644
--- a/hostsidetests/backup/includeexcludeapp/EncryptionAttributeApp/Android.bp
+++ b/hostsidetests/backup/includeexcludeapp/EncryptionAttributeApp/Android.bp
@@ -27,7 +27,6 @@
srcs: ["src/**/*.java"],
// tag this module as a cts test artifact
test_suites: [
- "arcts",
"cts",
"general-tests",
],
diff --git a/hostsidetests/backup/includeexcludeapp/FullBackupContentApp/Android.bp b/hostsidetests/backup/includeexcludeapp/FullBackupContentApp/Android.bp
index cb2fb1e..378c786 100644
--- a/hostsidetests/backup/includeexcludeapp/FullBackupContentApp/Android.bp
+++ b/hostsidetests/backup/includeexcludeapp/FullBackupContentApp/Android.bp
@@ -27,7 +27,6 @@
srcs: ["src/**/*.java"],
// tag this module as a cts test artifact
test_suites: [
- "arcts",
"cts",
"general-tests",
],
diff --git a/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java b/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java
index 6928718..fc355af 100644
--- a/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java
+++ b/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java
@@ -98,10 +98,6 @@
return device.isMultiUserSupported();
}
- protected boolean isMultiUserSupported() throws Exception {
- return isMultiUserSupported(getDevice());
- }
-
protected Map<String, String> createArgsFromLastTestRun() {
final Map<String, String> args = new HashMap<>();
for (String key : new String[] {
diff --git a/hostsidetests/blobstore/src/com/android/cts/host/blob/BlobStoreMultiUserTest.java b/hostsidetests/blobstore/src/com/android/cts/host/blob/BlobStoreMultiUserTest.java
index 9a78386..ec13654 100644
--- a/hostsidetests/blobstore/src/com/android/cts/host/blob/BlobStoreMultiUserTest.java
+++ b/hostsidetests/blobstore/src/com/android/cts/host/blob/BlobStoreMultiUserTest.java
@@ -40,9 +40,9 @@
@BeforeClassWithInfo
public static void setUpClass(TestInformation testInfo) throws Exception {
- if(!isMultiUserSupported(testInfo.getDevice())) {
- return;
- }
+ assumeTrue("Multi-user is not supported on this device",
+ isMultiUserSupported(testInfo.getDevice()));
+
mPrimaryUserId = testInfo.getDevice().getPrimaryUserId();
mSecondaryUserId = testInfo.getDevice().createUser("Test_User");
assertThat(testInfo.getDevice().startUser(mSecondaryUserId)).isTrue();
@@ -50,8 +50,6 @@
@Before
public void setUp() throws Exception {
- assumeTrue("Multi-user is not supported on this device", isMultiUserSupported());
-
for (String apk : new String[] {TARGET_APK, TARGET_APK_DEV}) {
installPackageAsUser(apk, true /* grantPermissions */, mPrimaryUserId, "-t");
installPackageAsUser(apk, true /* grantPermissions */, mSecondaryUserId, "-t");
diff --git a/hostsidetests/car/app/Android.bp b/hostsidetests/car/app/Android.bp
index 32e33d8..144028d 100644
--- a/hostsidetests/car/app/Android.bp
+++ b/hostsidetests/car/app/Android.bp
@@ -30,7 +30,7 @@
"compatibility-device-util-axt",
],
- libs: ["android.car"],
+ libs: ["android.car-test-stubs"],
}
android_test_helper_app {
diff --git a/hostsidetests/car/app/src/android/car/cts/app/CarWatchdogTestActivity.java b/hostsidetests/car/app/src/android/car/cts/app/CarWatchdogTestActivity.java
index 7057462..66c6c5a 100644
--- a/hostsidetests/car/app/src/android/car/cts/app/CarWatchdogTestActivity.java
+++ b/hostsidetests/car/app/src/android/car/cts/app/CarWatchdogTestActivity.java
@@ -299,6 +299,7 @@
CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
}
+ Log.d(TAG, "Fetched resource overuse stats: " + stats);
IoOveruseStats ioOveruseStats = stats.getIoOveruseStats();
if (ioOveruseStats == null) {
setDumpMessage(
@@ -312,7 +313,6 @@
+ "' returned by get request");
return 0;
}
- Log.d(TAG, ioOveruseStats.toString());
/*
* Check for foreground mode bytes given CtsCarApp is running in the foreground
* during testing.
@@ -343,26 +343,24 @@
@Override
public void onOveruse(ResourceOveruseStats resourceOveruseStats) {
synchronized (mLock) {
+ Log.d(TAG, "onOveruse callback received: " + resourceOveruseStats);
mForegroundModeBytes = -1;
mNotificationReceived = true;
mLock.notifyAll();
- }
- Log.d(TAG, resourceOveruseStats.toString());
- if (resourceOveruseStats.getIoOveruseStats() == null) {
- setDumpMessage(
- "ERROR: No I/O overuse stats reported for the application in the overuse "
- + "notification.");
- return;
- }
- long reportedWrittenBytes =
- resourceOveruseStats.getIoOveruseStats().getTotalBytesWritten();
- if (reportedWrittenBytes < mExpectedMinWrittenBytes) {
- setDumpMessage("ERROR: Actual written bytes to disk '" + mExpectedMinWrittenBytes
- + "' don't match written bytes '" + reportedWrittenBytes
- + "' reported in overuse notification");
- return;
- }
- synchronized (mLock) {
+ if (resourceOveruseStats.getIoOveruseStats() == null) {
+ setDumpMessage(
+ "ERROR: No I/O overuse stats reported for the application in the "
+ + "overuse notification.");
+ return;
+ }
+ long reportedWrittenBytes =
+ resourceOveruseStats.getIoOveruseStats().getTotalBytesWritten();
+ if (reportedWrittenBytes < mExpectedMinWrittenBytes) {
+ setDumpMessage("ERROR: Actual written bytes to disk '"
+ + mExpectedMinWrittenBytes + "' don't match written bytes '"
+ + reportedWrittenBytes + "' reported in overuse notification");
+ return;
+ }
mForegroundModeBytes =
resourceOveruseStats.getIoOveruseStats().getRemainingWriteBytes()
.getForegroundModeBytes();
diff --git a/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java b/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
index 595881a..be1aac1 100644
--- a/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
+++ b/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
@@ -270,14 +270,14 @@
* Creates a full user with car service shell command.
*/
protected int createFullUser(String name) throws Exception {
- return createUser(name, /* flags= */ 0, "android.os.usertype.full.SECONDARY");
+ return createUser(name, /* flags= */ 0, /* isGuest= */ false);
}
/**
* Creates a full guest with car service shell command.
*/
protected int createGuestUser(String name) throws Exception {
- return createUser(name, /* flags= */ 0, "android.os.usertype.full.GUEST");
+ return createUser(name, /* flags= */ 0, /* isGuest= */ true);
}
/**
@@ -285,14 +285,15 @@
*
* <p><b>NOTE: </b>it uses User HAL flags, not core Android's.
*/
- protected int createUser(String name, int flags, String type) throws Exception {
+ protected int createUser(String name, int flags, boolean isGuest) throws Exception {
name = USER_PREFIX + "." + name;
waitForCarServiceReady();
int userId = executeAndParseCommand(CREATE_USER_OUTPUT_PATTERN,
- "Could not create user with name " + name + ", flags " + flags + ", type" + type,
+ "Could not create user with name " + name
+ + ", flags " + flags + ", guest " + isGuest,
matcher -> Integer.parseInt(matcher.group(1)),
- "cmd car_service create-user --flags %d --type %s %s",
- flags, type, name);
+ "cmd car_service create-user --flags %d %s%s",
+ flags, (isGuest ? "--guest " : ""), name);
markUserForRemovalAfterTest(userId);
return userId;
}
diff --git a/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java b/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java
index f66b391..014c783 100644
--- a/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java
+++ b/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java
@@ -29,6 +29,7 @@
import com.android.os.AtomsProto.CarWatchdogIoOveruseStatsReported;
import com.android.os.AtomsProto.CarWatchdogKillStatsReported;
import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import org.junit.After;
@@ -36,6 +37,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
@@ -44,6 +46,8 @@
@RunWith(DeviceJUnit4ClassRunner.class)
public class CarWatchdogHostTest extends CarHostJUnit4TestCase {
+ public static final String TAG = CarWatchdogHostTest.class.getSimpleName();
+
/**
* CarWatchdog app package.
*/
@@ -120,9 +124,20 @@
private static final long WATCHDOG_ACTION_TIMEOUT_MS = 15_000;
+ private boolean mDidModifyDateTime;
private long mOriginalForegroundBytes;
@Before
+ public void dateSetUp() throws Exception {
+ checkAndSetDate();
+ }
+
+ @After
+ public void dateReset() throws Exception {
+ checkAndResetDate();
+ }
+
+ @Before
public void setUp() throws Exception {
ConfigUtils.removeConfig(getDevice());
ReportUtils.clearReports(getDevice());
@@ -328,4 +343,24 @@
"am start -W -a android.intent.action.MAIN -n %s/%s --el bytes_to_kill %d",
appPkg, ACTIVITY_CLASS, remainingBytes);
}
+
+ private void checkAndSetDate() throws Exception {
+ // Get date in ISO-8601 format
+ LocalDateTime now = LocalDateTime.parse(executeCommand("date +%%FT%%T").trim());
+ if (now.getHour() < 23) {
+ return;
+ }
+ executeCommand("date %s", now.minusHours(1));
+ CLog.d(TAG, "DateTime changed from %s to %s", now, now.minusHours(1));
+ mDidModifyDateTime = true;
+ }
+
+ private void checkAndResetDate() throws Exception {
+ if (!mDidModifyDateTime) {
+ return;
+ }
+ LocalDateTime now = LocalDateTime.parse(executeCommand("date +%%FT%%T").trim());
+ executeCommand("date %s", now.plusHours(1));
+ CLog.d(TAG, "DateTime changed from %s to %s", now, now.plusHours(1));
+ }
}
diff --git a/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java b/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java
index cc8823a..3eb85a9 100644
--- a/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java
+++ b/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java
@@ -94,7 +94,7 @@
Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
- AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
atom -> atom.getGarageModeInfo().getIsGarageMode() ? 1 : 0);
}
diff --git a/hostsidetests/car/src/android/car/cts/PreCreateUsersHostTest.java b/hostsidetests/car/src/android/car/cts/PreCreateUsersHostTest.java
index 1d5ebcf..604adab 100644
--- a/hostsidetests/car/src/android/car/cts/PreCreateUsersHostTest.java
+++ b/hostsidetests/car/src/android/car/cts/PreCreateUsersHostTest.java
@@ -164,7 +164,7 @@
: createFullUser("PreCreatedUsersTest_Reference_User");
// Some permissions (e.g. Role permission) are given only after initialization.
switchUser(referenceUserId);
- waitUntilUserPermissionsIsReady(referenceUserId);
+ waitUntilUserPermissionsIsReady(/* waitTime= */ 30, referenceUserId);
Map<String, List<String>> refPkgMap = getPackagesAndPermissionsForUser(referenceUserId);
// There can be just one guest by default, so remove it now otherwise
@@ -182,7 +182,7 @@
convertPreCreatedUser(isGuest, preCreatedUserId);
// Some permissions (e.g. Role permission) are given only after initialization.
switchUser(preCreatedUserId);
- waitUntilUserPermissionsIsReady(preCreatedUserId);
+ waitUntilUserPermissionsIsReady(/* waitTime= */ 20, preCreatedUserId);
Map<String, List<String>> actualPkgMap = getPackagesAndPermissionsForUser(preCreatedUserId);
List<String> errors = new ArrayList<>();
@@ -192,6 +192,20 @@
.that(actualPkgMap.get(pkg))
.isEqualTo(refPkgMap.get(pkg)));
}
+
+ if (!errors.isEmpty()) {
+ // if there are errors, wait for some more time and check again.
+ waitUntilUserPermissionsIsReady(/* waitTime= */ 20, preCreatedUserId);
+ Map<String, List<String>> actualPkgMap2 = getPackagesAndPermissionsForUser(
+ preCreatedUserId);
+
+ errors = new ArrayList<>();
+ for (String pkg : refPkgMap.keySet()) {
+ addError(errors, () -> assertWithMessage("permissions state mismatch for %s", pkg)
+ .that(actualPkgMap2.get(pkg))
+ .isEqualTo(refPkgMap.get(pkg)));
+ }
+ }
assertWithMessage("found %s error", errors.size()).that(errors).isEmpty();
}
@@ -246,10 +260,10 @@
}
// TODO(b/170263003): update this method after core framewokr's refactoring for proto
- private void waitUntilUserPermissionsIsReady(int userId) throws InterruptedException {
- int napTimeSec = 20;
- CLog.i("Sleeping %ds to make permissions for user %d is ready", napTimeSec, userId);
- sleep(napTimeSec * 1_000);
+ private void waitUntilUserPermissionsIsReady(int waitTime, int userId)
+ throws InterruptedException {
+ CLog.i("Sleeping %ds to make permissions for user %d is ready", waitTime, userId);
+ sleep(waitTime * 1_000);
}
private void deletePreCreatedUsers() throws Exception {
diff --git a/hostsidetests/car/src/android/car/cts/powerpolicy/CpmsFrameworkLayerStateInfo.java b/hostsidetests/car/src/android/car/cts/powerpolicy/CpmsFrameworkLayerStateInfo.java
index d7f6108..08abe56 100644
--- a/hostsidetests/car/src/android/car/cts/powerpolicy/CpmsFrameworkLayerStateInfo.java
+++ b/hostsidetests/car/src/android/car/cts/powerpolicy/CpmsFrameworkLayerStateInfo.java
@@ -23,6 +23,8 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
public final class CpmsFrameworkLayerStateInfo {
private static final int STRING_BUILDER_BUF_SIZE = 1024;
@@ -302,12 +304,14 @@
int val = 0;
switch (header) {
case CURRENT_STATE_HDR:
- String[] tokens = mLines[mIdx].split(",*\\s");
- if (tokens.length != 6) {
+ Pattern pattern = Pattern.compile("mCurrentState: CpmsState "
+ + "[^\\n]*carPowerStateListenerState=(\\d+)");
+ Matcher matcher = pattern.matcher(mLines[mIdx]);
+ if (!matcher.find()) {
throw new IllegalArgumentException("malformatted mCurrentState: "
+ mLines[mIdx]);
}
- val = Integer.parseInt(tokens[4].trim().substring(tokens[4].length() - 1));
+ val = Integer.parseInt(matcher.group(1));
break;
case NUMBER_POLICY_LISTENERS_HDR:
int strLen = mLines[mIdx].length();
diff --git a/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyConstants.java b/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyConstants.java
index f0fbbab..9bdd2ef 100644
--- a/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyConstants.java
+++ b/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyConstants.java
@@ -20,6 +20,8 @@
public static final int VHAL_POWER_STATE_REQ_PROPERTY_ID = 289475072;
public static final int VHAL_POWER_STATE_REP_PROPERTY_ID = 289475073;
+ private PowerPolicyConstants() { }
+
public static final class CarPowerState {
public static final int INVALID = 0;
public static final int WAIT_FOR_VHAL = 1;
@@ -29,6 +31,8 @@
public static final int ON = 6;
public static final int SHUTDOWN_PREPARE = 7;
public static final int SHUTDOWN_CANCELLED = 8;
+
+ private CarPowerState() { }
}
public static final class VhalPowerStateReq {
@@ -36,6 +40,8 @@
public static final int SHUTDOWN_PREPARE = 1;
public static final int CANCEL_SHUTDOWN = 2;
public static final int FINISHED = 3;
+
+ private VhalPowerStateReq() { }
}
public static final class ShutdownParam {
@@ -44,5 +50,7 @@
public static final int CAN_SLEEP = 2;
public static final int SHUTDOWN_ONLY = 3;
public static final int SLEEP_IMMEDIATELY = 4;
+
+ private ShutdownParam() { }
}
}
diff --git a/hostsidetests/car_builtin/Android.bp b/hostsidetests/car_builtin/Android.bp
new file mode 100644
index 0000000..fc8f7d9
--- /dev/null
+++ b/hostsidetests/car_builtin/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+ name: "CtsCarBuiltinApiHostTestCases",
+ defaults: ["cts_defaults"],
+ // Only compile source java files in this apk.
+ srcs: [
+ "src/**/*.java",
+ ],
+ libs: [
+ "tradefed",
+ "compatibility-host-util",
+ "truth-prebuilt",
+ ],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ static_libs: [
+ "cts-statsd-atom-host-test-utils",
+ ],
+}
diff --git a/hostsidetests/car_builtin/AndroidTest.xml b/hostsidetests/car_builtin/AndroidTest.xml
new file mode 100644
index 0000000..a4fcf49
--- /dev/null
+++ b/hostsidetests/car_builtin/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="Car Builtin APIs Host Tests">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="auto" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsCarBuiltinApiHostTestCases.jar" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+</configuration>
diff --git a/hostsidetests/car_builtin/OWNERS b/hostsidetests/car_builtin/OWNERS
new file mode 100644
index 0000000..f0afac5
--- /dev/null
+++ b/hostsidetests/car_builtin/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 526680
+keunyoung@google.com
+felipeal@google.com
+gurunagarajan@google.com
diff --git a/hostsidetests/car_builtin/src/android/car/cts/builtin/BinderHelperHostTest.java b/hostsidetests/car_builtin/src/android/car/cts/builtin/BinderHelperHostTest.java
new file mode 100644
index 0000000..b8aa638
--- /dev/null
+++ b/hostsidetests/car_builtin/src/android/car/cts/builtin/BinderHelperHostTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin;
+
+import static android.car.cts.builtin.os.GetInitialUserInfoCommand.OK_STATUS_RETURN_HEADER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.cts.builtin.os.GetInitialUserInfoCommand;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class BinderHelperHostTest extends BaseHostJUnit4Test {
+
+ // When a car shell command (such as, "cmd car_service get-do-activities") is called, it
+ // triggers both BinderHelper.onTransactForCmd and
+ // BinderHelper.ShellCommandListener.onShellCommand calls.
+ @Test
+ public void testOnTransactForCmd() throws Exception {
+ // setup
+ GetInitialUserInfoCommand infoCmd = new GetInitialUserInfoCommand(getDevice());
+
+ // execution and assertion
+ infoCmd.executeWith();
+ assertThat(infoCmd.returnStartsWith(OK_STATUS_RETURN_HEADER)).isTrue();
+ }
+}
diff --git a/hostsidetests/car_builtin/src/android/car/cts/builtin/CtsCarShellCommand.java b/hostsidetests/car_builtin/src/android/car/cts/builtin/CtsCarShellCommand.java
new file mode 100644
index 0000000..6ffaf40
--- /dev/null
+++ b/hostsidetests/car_builtin/src/android/car/cts/builtin/CtsCarShellCommand.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin;
+
+import com.android.tradefed.device.ITestDevice;
+
+/**
+ * Base class for all Car Builtin API test related shell command invocation.
+ *
+ * It is the extended subclass command to construct the shell command string and decide
+ * if the command's return is successful or not. Further, it is the extended subclass command
+ * to extra all necessary information from the return string if needed.
+ */
+public abstract class CtsCarShellCommand {
+ private final ITestDevice mDevice;
+
+ protected final String mCommand;
+ protected String[] mCommandArgs;
+ protected String mCommandReturn;
+
+ protected CtsCarShellCommand(String commandName, ITestDevice device) {
+ mCommand = commandName;
+ mDevice = device;
+ }
+
+ public CtsCarShellCommand executeWith(String... args) throws Exception {
+ mCommandArgs = args;
+
+ String cmd = mCommand;
+ if (mCommandArgs != null && mCommandArgs.length > 0) {
+ cmd = mCommand + " " + String.join(" ", mCommandArgs);
+ }
+ mCommandReturn = mDevice.executeShellCommand(cmd);
+ parseCommandReturn();
+ return this;
+ }
+
+ public boolean returnStartsWith(String str) throws Exception {
+ if (mCommandReturn == null) {
+ throw new Exception("command return is null. not executed?");
+ }
+ return mCommandReturn.startsWith(str);
+ }
+
+ protected abstract void parseCommandReturn() throws Exception;
+}
diff --git a/hostsidetests/car_builtin/src/android/car/cts/builtin/LockPatternHelperHostTest.java b/hostsidetests/car_builtin/src/android/car/cts/builtin/LockPatternHelperHostTest.java
new file mode 100644
index 0000000..e597c65
--- /dev/null
+++ b/hostsidetests/car_builtin/src/android/car/cts/builtin/LockPatternHelperHostTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.cts.builtin.widget.CheckLockIsSecureCommand;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class LockPatternHelperHostTest extends BaseHostJUnit4Test {
+
+ @Test
+ public void testIsSecureApi() throws Exception {
+ // setup
+ CheckLockIsSecureCommand checkLockCmd = new CheckLockIsSecureCommand(getDevice());
+ String userId = String.valueOf(getDevice().getCurrentUser());
+
+ // execution and assertion
+ checkLockCmd.executeWith(userId);
+ // as the current user does not have any credentials, expect to be false
+ assertThat(checkLockCmd.isSecure()).isFalse();
+ }
+}
diff --git a/hostsidetests/car_builtin/src/android/car/cts/builtin/os/GetInitialUserInfoCommand.java b/hostsidetests/car_builtin/src/android/car/cts/builtin/os/GetInitialUserInfoCommand.java
new file mode 100644
index 0000000..f26d59b
--- /dev/null
+++ b/hostsidetests/car_builtin/src/android/car/cts/builtin/os/GetInitialUserInfoCommand.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.os;
+
+import android.car.cts.builtin.CtsCarShellCommand;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+public final class GetInitialUserInfoCommand extends CtsCarShellCommand {
+
+ public static final String OK_STATUS_RETURN_HEADER = "Call status: OK";
+ private static final String COMMAND_NAME = "cmd car_service get-initial-user-info FIRST_BOOT";
+
+ public GetInitialUserInfoCommand(ITestDevice device) {
+ super(COMMAND_NAME, device);
+ }
+
+ @Override
+ protected void parseCommandReturn() throws Exception {
+ if (mCommandArgs.length != 0) {
+ throw new IllegalArgumentException("No argument is expected");
+ }
+ CLog.d(mCommand + " command returns: " + mCommandReturn);
+ }
+}
diff --git a/hostsidetests/car_builtin/src/android/car/cts/builtin/widget/CheckLockIsSecureCommand.java b/hostsidetests/car_builtin/src/android/car/cts/builtin/widget/CheckLockIsSecureCommand.java
new file mode 100644
index 0000000..722acff
--- /dev/null
+++ b/hostsidetests/car_builtin/src/android/car/cts/builtin/widget/CheckLockIsSecureCommand.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.widget;
+
+import android.car.cts.builtin.CtsCarShellCommand;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+public final class CheckLockIsSecureCommand extends CtsCarShellCommand {
+ public static final String COMMAND_NAME = "cmd car_service check-lock-is-secure";
+
+ private boolean mIsSecure = false;
+
+ public CheckLockIsSecureCommand(ITestDevice device) {
+ super(COMMAND_NAME, device);
+ }
+
+ public boolean isSecure() {
+ return mIsSecure;
+ }
+
+ @Override
+ protected void parseCommandReturn() throws Exception {
+ if (mCommandArgs.length != 1) {
+ throw new IllegalArgumentException("user id is expected");
+ }
+
+ mIsSecure = Boolean.parseBoolean(mCommandReturn.trim());
+
+ CLog.d(mCommand + " command returns: " + mCommandReturn);
+ CLog.d("CheckLockIsSecureCommand.mIsSecure: " + mIsSecure);
+ }
+}
diff --git a/hostsidetests/compilation/Android.bp b/hostsidetests/compilation/Android.bp
index 6f4441f..e6ad6a2 100644
--- a/hostsidetests/compilation/Android.bp
+++ b/hostsidetests/compilation/Android.bp
@@ -31,5 +31,6 @@
"tradefed",
"compatibility-host-util",
"guava",
+ "truth-prebuilt",
],
}
diff --git a/hostsidetests/compilation/AndroidTest.xml b/hostsidetests/compilation/AndroidTest.xml
index da1e27d..76f23e5 100644
--- a/hostsidetests/compilation/AndroidTest.xml
+++ b/hostsidetests/compilation/AndroidTest.xml
@@ -22,6 +22,6 @@
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsCompilationTestCases.jar" />
- <option name="runtime-hint" value="9m45s" />
+ <option name="runtime-hint" value="14m" />
</test>
</configuration>
diff --git a/hostsidetests/compilation/src/android/compilation/cts/BackgroundDexOptimizationTest.java b/hostsidetests/compilation/src/android/compilation/cts/BackgroundDexOptimizationTest.java
new file mode 100644
index 0000000..edcfaa4
--- /dev/null
+++ b/hostsidetests/compilation/src/android/compilation/cts/BackgroundDexOptimizationTest.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.compilation.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceTestCase;
+
+import com.google.common.io.ByteStreams;
+
+import junit.framework.Assert;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Tests background dex optimization which runs as idle job.
+ */
+public final class BackgroundDexOptimizationTest extends DeviceTestCase {
+ private static final long REBOOT_TIMEOUT_MS = 600_000;
+ private static final long DEXOPT_TIMEOUT_MS = 1_200_000;
+ // Cancel should be faster. It will be usually much shorter but we cannot make it too short
+ // as CTS cannot enforce unspecified performance.
+ private static final long DEXOPT_CANCEL_TIMEOUT_MS = 10_000;
+ private static final long POLLING_TIME_SLICE = 2;
+
+ private static final String CMD_DUMP_PACKAGE_DEXOPT = "dumpsys -t 100 package dexopt";
+
+ private static final String CMD_START_POST_BOOT = "cmd jobscheduler run android 801";
+ private static final String CMD_CANCEL_POST_BOOT = "cmd jobscheduler timeout android 801";
+ private static final String CMD_START_IDLE = "cmd jobscheduler run android 800";
+ private static final String CMD_CANCEL_IDLE = "cmd jobscheduler timeout android 800";
+
+ private static final String APPLICATION_PACKAGE = "android.compilation.cts";
+ private static final String APPLICATION_APK = "CtsCompilationApp";
+ private static final String CMD_APP_ACTIVITY_LAUNCH =
+ "am start -n " + APPLICATION_PACKAGE + "/.CompilationTargetActivity";
+
+ private static final String CMD_DELETE_ODEX = "pm delete-dexopt " + APPLICATION_PACKAGE;
+
+ private static final boolean DBG_LOG_CMD = false;
+
+ // Uses internal consts defined in BackgroundDexOptService only for testing purpose.
+ private static final int STATUS_OK = 0;
+ private static final int STATUS_CANCELLED = 1;
+
+ private ITestDevice mDevice;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mDevice = getDevice();
+ // Should reboot to put the device into known states (= post boot optimization not run yet).
+ mDevice.reboot();
+ assertThat(mDevice.waitForBootComplete(REBOOT_TIMEOUT_MS)).isTrue();
+ // This requires PackageManager to be alive. So run after reboot as the previous failure
+ // may have device in booting state.
+ assumeTrue(checkDexOptEnabled());
+ }
+
+ public void testPostBootOptimizationCompleted() throws Exception {
+ // Note that post boot job runs only once until it is completed.
+ completePostBootOptimization();
+ }
+
+ public void testPostBootOptimizationCancelled() throws Exception {
+ reinstallAppPackage();
+ LastDeviceExecutionTime timeBefore = getLastExecutionTime();
+ postJobSchedulerJob(CMD_START_POST_BOOT);
+
+ // Wait until it is started.
+ pollingCheck("Post boot start timeout", DEXOPT_TIMEOUT_MS,
+ () -> getLastExecutionTime().startTime >= timeBefore.deviceCurrentTime);
+
+ // Now cancel it.
+ executeShellCommand(CMD_CANCEL_POST_BOOT);
+
+ // Wait until it is completed or cancelled. We cannot prevent faster devices with small
+ // number of APKs to complete very quickly, so completion while cancelling can happen.
+ pollingCheck("Post boot cancel timeout", DEXOPT_CANCEL_TIMEOUT_MS,
+ () -> getLastExecutionTime().duration >= 0);
+
+ int status = getLastDexOptStatus();
+ assertThat(status).isAnyOf(STATUS_OK, STATUS_CANCELLED);
+ if (status == STATUS_CANCELLED) {
+ assertThat(checkFinishedPostBootUpdate()).isFalse();
+ // If cancelled, we can complete it by running it again.
+ completePostBootOptimization();
+ } else {
+ assertThat(checkFinishedPostBootUpdate()).isTrue();
+ }
+ }
+
+ public void testIdleOptimizationCompleted() throws Exception {
+ completePostBootOptimization();
+
+ completeIdleOptimization();
+ // idle job can run again.
+ completeIdleOptimization();
+ }
+
+ public void testIdleOptimizationCancelled() throws Exception {
+ completePostBootOptimization();
+
+ reinstallAppPackage();
+ LastDeviceExecutionTime timeBefore = getLastExecutionTime();
+ postJobSchedulerJob(CMD_START_IDLE);
+
+ // Wait until it is started.
+ pollingCheck("Idle start timeout", DEXOPT_TIMEOUT_MS,
+ () -> getLastExecutionTime().startTime >= timeBefore.deviceCurrentTime);
+
+ // Now cancel it.
+ executeShellCommand(CMD_CANCEL_IDLE);
+
+ // Wait until it is completed or cancelled.
+ pollingCheck("Idle cancel timeout", DEXOPT_CANCEL_TIMEOUT_MS,
+ () -> getLastExecutionTime().duration >= 0);
+
+ int status = getLastDexOptStatus();
+ assertThat(status).isAnyOf(STATUS_OK, STATUS_CANCELLED);
+ if (status == STATUS_CANCELLED) {
+ // If cancelled, we can complete it by running it again.
+ completeIdleOptimization();
+ }
+ }
+
+ private String executeShellCommand(String cmd) throws Exception {
+ String result = mDevice.executeShellCommand(cmd);
+ if (DBG_LOG_CMD) {
+ CLog.i("Executed cmd:" + cmd + ", result:" + result);
+ }
+ return result;
+ }
+
+ private void completePostBootOptimization() throws Exception {
+ reinstallAppPackage();
+ LastDeviceExecutionTime timeBefore = getLastExecutionTime();
+ postJobSchedulerJob(CMD_START_POST_BOOT);
+
+ pollingCheck("Post boot optimization timeout", DEXOPT_TIMEOUT_MS,
+ () -> checkFinishedPostBootUpdate());
+
+ LastDeviceExecutionTime timeAfter = getLastExecutionTime();
+ assertThat(timeAfter.startTime).isAtLeast(timeBefore.deviceCurrentTime);
+ assertThat(timeAfter.duration).isAtLeast(0);
+ int status = getLastDexOptStatus();
+ assertThat(status).isEqualTo(STATUS_OK);
+ }
+
+ private void completeIdleOptimization() throws Exception {
+ reinstallAppPackage();
+ LastDeviceExecutionTime timeBefore = getLastExecutionTime();
+ postJobSchedulerJob(CMD_START_IDLE);
+
+ pollingCheck("Idle optimization timeout", DEXOPT_TIMEOUT_MS,
+ () -> {
+ LastDeviceExecutionTime executionTime = getLastExecutionTime();
+ return executionTime.startTime >= timeBefore.deviceCurrentTime
+ && executionTime.duration >= 0;
+ });
+
+ int status = getLastDexOptStatus();
+ assertThat(status).isEqualTo(STATUS_OK);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Cancel all active dexopt jobs.
+ executeShellCommand(CMD_CANCEL_IDLE);
+ executeShellCommand(CMD_CANCEL_POST_BOOT);
+ mDevice.uninstallPackage(APPLICATION_PACKAGE);
+ super.tearDown();
+ }
+
+ private void postJobSchedulerJob(String cmd) throws Exception {
+ // Do retry as job may not be registered yet during boot up.
+ pollingCheck("Starting job timeout:" + cmd, DEXOPT_TIMEOUT_MS,
+ () -> {
+ String r = executeShellCommand(cmd);
+ return r.contains("Running");
+ });
+ }
+
+ private void reinstallAppPackage() throws Exception {
+ mDevice.uninstallPackage(APPLICATION_PACKAGE);
+
+ File apkFile = File.createTempFile(APPLICATION_APK, ".apk");
+ try (OutputStream outputStream = new FileOutputStream(apkFile)) {
+ InputStream inputStream = getClass().getResourceAsStream(
+ "/" + APPLICATION_APK + ".apk");
+ ByteStreams.copy(inputStream, outputStream);
+ }
+ String error = mDevice.installPackage(apkFile, /* reinstall= */ false);
+
+ assertThat(error).isNull();
+
+ // Delete odex files.
+ executeShellCommand(CMD_DELETE_ODEX);
+ executeShellCommand(CMD_APP_ACTIVITY_LAUNCH);
+ // Give short time to run some code.
+ Thread.sleep(500);
+ }
+
+ private boolean checkDexOptEnabled() throws Exception {
+ return checkBooleanDumpValue("enabled");
+ }
+
+ private boolean checkFinishedPostBootUpdate() throws Exception {
+ return checkBooleanDumpValue("mFinishedPostBootUpdate");
+ }
+
+ private boolean checkBooleanDumpValue(String key) throws Exception {
+ String value = findDumpValueForKey(key);
+ assertThat(value).isNotNull();
+ return value.equals("true");
+ }
+
+ private String findDumpValueForKey(String key) throws Exception {
+ for (String line: getDexOptDumpForBgDexOpt()) {
+ String[] vals = line.split(":");
+ if (vals[0].equals(key)) {
+ return vals[1];
+ }
+ }
+ return null;
+ }
+
+ private List<String> getDexOptDumpForBgDexOpt() throws Exception {
+ String dump = executeShellCommand(CMD_DUMP_PACKAGE_DEXOPT);
+ String[] lines = dump.split("\n");
+ LinkedList<String> bgDexOptDumps = new LinkedList<>();
+ // BgDexopt state is located in the last part from the dexopt dump. So there is no separate
+ // end of the dump check.
+ boolean inBgDexOptDump = false;
+ for (int i = 0; i < lines.length; i++) {
+ if (lines[i].contains("BgDexopt state:")) {
+ inBgDexOptDump = true;
+ } else if (inBgDexOptDump) {
+ bgDexOptDumps.add(lines[i].trim());
+ }
+ }
+ // dumpsys package can expire due to the lock while bgdexopt is running.
+ if (dump.contains("DUMP TIMEOUT")) {
+ CLog.w("package dump timed out");
+ throw new TimeoutException();
+ }
+ return bgDexOptDumps;
+ }
+
+ private int getLastDexOptStatus() throws Exception {
+ String value = findDumpValueForKey("mLastExecutionStatus");
+ assertThat(value).isNotNull();
+ return Integer.parseInt(value);
+ }
+
+ private LastDeviceExecutionTime getLastExecutionTime() throws Exception {
+ long startTime = 0;
+ long duration = 0;
+ long deviceCurrentTime = 0;
+ for (String line: getDexOptDumpForBgDexOpt()) {
+ String[] vals = line.split(":");
+ switch (vals[0]) {
+ case "mLastExecutionStartTimeMs":
+ startTime = Long.parseLong(vals[1]);
+ break;
+ case "mLastExecutionDurationMs":
+ duration = Long.parseLong(vals[1]);
+ break;
+ case "now":
+ deviceCurrentTime = Long.parseLong(vals[1]);
+ break;
+ }
+ }
+ assertThat(deviceCurrentTime).isNotEqualTo(0);
+ return new LastDeviceExecutionTime(startTime, duration, deviceCurrentTime);
+ }
+
+ private static void pollingCheck(CharSequence message, long timeout,
+ Callable<Boolean> condition) throws Exception {
+ long expirationTime = System.currentTimeMillis() + timeout;
+ while (System.currentTimeMillis() < expirationTime) {
+ try {
+ if (condition.call()) {
+ return;
+ }
+ } catch (TimeoutException e) {
+ // DUMP TIMEOUT has happened. Ignore it as we have to retry.
+ }
+ Thread.sleep(POLLING_TIME_SLICE);
+ }
+
+ Assert.fail(message.toString());
+ }
+
+ private static class LastDeviceExecutionTime {
+ public final long startTime;
+ public final long duration;
+ public final long deviceCurrentTime;
+
+ private LastDeviceExecutionTime(long startTime, long duration, long deviceCurrentTime) {
+ this.startTime = startTime;
+ this.duration = duration;
+ this.deviceCurrentTime = deviceCurrentTime;
+ }
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
index 89c72ed..5b19966 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
@@ -31,6 +31,8 @@
<action android:name="com.android.cts.certinstaller.remove_cert"/>
<action android:name="com.android.cts.certinstaller.verify_cert"/>
<action android:name="com.android.cts.certinstaller.install_keypair"/>
+ <action android:name="com.android.cts.certinstaller.done"/>
+ <action android:name="com.android.cts.certinstaller.read_esid"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsStartActivityTest.java b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsStartActivityTest.java
index 7332521..6925810 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsStartActivityTest.java
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsStartActivityTest.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.START_CROSS_PROFILE_ACTIVITIES;
import static com.google.common.truth.Truth.assertThat;
@@ -130,6 +131,19 @@
String.valueOf(mUserSerialNumber), textView.getText());
}
+ @Test(expected = SecurityException.class)
+ public void testCannotStartActivityByIntentWithStartCrossProfileActivitiesPermission() {
+ Intent intent = new Intent();
+ intent.setComponent(MainActivity.getComponentName(mContext));
+ ShellIdentityUtils.dropShellPermissionIdentity();
+
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+ mCrossProfileApps,
+ crossProfileApps -> crossProfileApps.startActivity(
+ intent, mTargetUser, /* callingActivity= */ null),
+ START_CROSS_PROFILE_ACTIVITIES);
+ }
+
@Test
public void testCanStartActivityByIntentWithInteractAcrossUsersPermission() {
Intent intent = new Intent();
@@ -329,6 +343,25 @@
}
@Test
+ public void testCanStartMainActivityByComponentWithStartCrossProfileActivitiesPermission() {
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mCrossProfileApps,
+ crossProfileApps -> mCrossProfileApps.startActivity(
+ MainActivity.getComponentName(mContext), mTargetUser),
+ START_CROSS_PROFILE_ACTIVITIES);
+
+ // 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 main activity in target user", textView);
+ assertEquals("Main Activity is started in wrong user",
+ String.valueOf(mUserSerialNumber), textView.getText());
+ } catch (Exception e) {
+ fail("unable to start main activity via CrossProfileApps#startActivity: " + e);
+ }
+ }
+
+ @Test
public void testCanStartNonMainActivityByComponent() {
try {
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mCrossProfileApps,
@@ -347,6 +380,25 @@
}
@Test
+ public void testCanStartNonMainActivityByComponentWithStartCrossProfileActivitiesPermission() {
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mCrossProfileApps,
+ crossProfileApps -> mCrossProfileApps.startActivity(
+ NonMainActivity.getComponentName(mContext), mTargetUser),
+ START_CROSS_PROFILE_ACTIVITIES);
+
+ // Look for the text view to verify that NonMainActivity is started.
+ UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW_NONMAIN)),
+ TIMEOUT_WAIT_UI);
+ assertNotNull("Failed to start non-main activity in target user", textView);
+ assertEquals("Non-Main Activity is started in wrong user",
+ String.valueOf(mUserSerialNumber), textView.getText());
+ } catch (Exception e) {
+ fail("unable to start non-main activity via CrossProfileApps#startActivity: " + e);
+ }
+ }
+
+ @Test
public void testCanStartNotExportedActivityByIntent() throws Exception {
Intent nonExportedActivityIntent = new Intent();
nonExportedActivityIntent.setComponent(NonExportedActivity.getComponentName(mContext));
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/OWNERS b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/OWNERS
index 61a60e4..ad51a93 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/OWNERS
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/OWNERS
@@ -1,3 +1,3 @@
# Bug component: 168445
file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
-pbdr@google.com
\ No newline at end of file
+pbdr@google.com
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml
index 572b40b..c081f95 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml
@@ -34,7 +34,13 @@
</receiver>
<!-- TODO(b/176993670): remove if DpmWrapper goes away -->
<receiver android:name="com.android.bedstead.dpmwrapper.IpcBroadcastReceiver"
- android:exported="true"/>
+ android:exported="true">
+ <!-- TODO(b/213348113, b/213331396) - might need to explicitly set the filter below:
+ <intent-filter>
+ <action android:name="com.android.bedstead.dpmwrapper.action.WRAPPED_MANAGER_CALL"/>
+ </intent-filter>
+ -->
+ </receiver>
<!-- TODO(b/176993670): remove if DpmWrapper goes away -->
<receiver android:name="com.android.bedstead.dpmwrapper.TestAppCallbacksReceiver"
android:exported="true"/>
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/GeneralDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/GeneralDelegateTest.java
index b93b884..8e0f517 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/GeneralDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/GeneralDelegateTest.java
@@ -16,6 +16,7 @@
package com.android.cts.delegate;
import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
import android.os.Bundle;
import android.test.MoreAsserts;
import android.util.Log;
@@ -57,4 +58,18 @@
expected.getMessage());
}
}
+
+ public void testSettingAdminComponentNameThrowsException() {
+ final String myPackageName = getInstrumentation().getContext().getPackageName();
+ final ComponentName myComponentName = new ComponentName(myPackageName,
+ GeneralDelegateTest.class.getName());
+
+ try {
+ mDpm.setUninstallBlocked(myComponentName, myPackageName, true);
+ fail("Expected SecurityException not thrown");
+ } catch (SecurityException expected) {
+ MoreAsserts.assertContainsRegex("does not exist or is not owned by uid",
+ expected.getMessage());
+ }
+ }
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java
deleted file mode 100644
index 0f4f22d..0000000
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java
+++ /dev/null
@@ -1,51 +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.deviceadmin;
-
-import android.os.Build;
-
-/**
- * Tests that:
- * - need to be run as device admin (as opposed to device owner) and
- * - require resetting the password at the end.
- *
- * Note: when adding a new method, make sure to add a corresponding method in
- * BaseDeviceAdminHostSideTest.
- */
-public class DeviceAdminPasswordTest extends BaseDeviceAdminTest {
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- assertNotDeviceOwner();
- }
-
- public void testResetPasswordDeprecated() {
- if (getTargetSdkLevel() < Build.VERSION_CODES.N) {
- assertFalse(dpm.resetPassword("1234", 0));
- } else {
- try {
- dpm.resetPassword("1234", 0);
- fail("resetPassword() should throw SecurityException");
- } catch (SecurityException e) { }
- }
- }
-
- private int getTargetSdkLevel() {
- return mContext.getApplicationContext().getApplicationInfo().targetSdkVersion;
- }
-}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
index 22c17a2..baaac8d 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
@@ -65,10 +65,6 @@
mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
}
- public void testLockNowLogged() {
- mDevicePolicyManager.lockNow(0);
- }
-
public void testSetKeyguardDisabledFeaturesLogged() {
mDevicePolicyManager.setKeyguardDisabledFeatures(
ADMIN_RECEIVER_COMPONENT, KEYGUARD_DISABLE_FEATURES_NONE);
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NearbyAppStreamingPolicyTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NearbyAppStreamingPolicyTest.java
index 23de557..b0ef424 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NearbyAppStreamingPolicyTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NearbyAppStreamingPolicyTest.java
@@ -22,9 +22,9 @@
public class NearbyAppStreamingPolicyTest extends BaseDeviceAdminTest {
- public void testGetNearbyAppStreamingPolicy_getsNearbyStreamingDisabledAsDefault() {
+ public void testGetNearbyAppStreamingPolicy_defaultToSameManagedAccountOnly() {
assertThat(mDevicePolicyManager.getNearbyAppStreamingPolicy())
- .isEqualTo(DevicePolicyManager.NEARBY_STREAMING_DISABLED);
+ .isEqualTo(DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY);
}
public void testSetNearbyAppStreamingPolicy_changesPolicy() {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NearbyNotificationStreamingPolicyTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NearbyNotificationStreamingPolicyTest.java
index ae0a26f..cd5abf3 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NearbyNotificationStreamingPolicyTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NearbyNotificationStreamingPolicyTest.java
@@ -22,9 +22,9 @@
public class NearbyNotificationStreamingPolicyTest extends BaseDeviceAdminTest {
- public void testGetNearbyNotificationStreamingPolicy_getsNearbyStreamingDisabledAsDefault() {
+ public void testGetNearbyNotificationStreamingPolicy_defaultToSameManagedAccountOnly() {
assertThat(mDevicePolicyManager.getNearbyNotificationStreamingPolicy())
- .isEqualTo(DevicePolicyManager.NEARBY_STREAMING_DISABLED);
+ .isEqualTo(DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY);
}
public void testSetNearbyNotificationStreamingPolicy_changesPolicy() {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordTest.java
deleted file mode 100644
index c660309..0000000
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordTest.java
+++ /dev/null
@@ -1,48 +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.deviceandprofileowner;
-
-import android.os.Build;
-
-
-/**
- * Test cases for {@link android.app.admin.DevicePolicyManager#resetPassword(String, int)}.
- *
- * As of R, resetPassword is fully deprecated: DPCs targeting Sdk level O or above will continue
- * to receive a SecurityException when calling this, while DPC targeting N or below will just get
- * a silent failure of API returning {@code false}. This class tests these two negative cases.
- *
- */
-public class ResetPasswordTest extends BaseDeviceAdminTest {
-
- public void testResetPasswordDeprecated() {
- waitUntilUserUnlocked();
-
- if (getTargetSdkLevel() >= Build.VERSION_CODES.O) {
- try {
- mDevicePolicyManager.resetPassword("1234", 0);
- fail("resetPassword() should throw SecurityException");
- } catch (SecurityException e) { }
-
- } else {
- assertFalse(mDevicePolicyManager.resetPassword("1234", 0));
- }
- }
-
- private int getTargetSdkLevel() {
- return mContext.getApplicationContext().getApplicationInfo().targetSdkVersion;
- }
-}
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 54bd5d4..621a15d 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
@@ -73,6 +73,11 @@
UserManager.DISALLOW_BLUETOOTH_SHARING,
UserManager.DISALLOW_CAMERA_TOGGLE,
UserManager.DISALLOW_MICROPHONE_TOGGLE,
+ UserManager.DISALLOW_CHANGE_WIFI_STATE,
+ UserManager.DISALLOW_WIFI_TETHERING,
+ UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
+ UserManager.DISALLOW_WIFI_DIRECT,
+ UserManager.DISALLOW_ADD_WIFI_CONFIG
};
/**
@@ -96,6 +101,10 @@
// UserManager.DISALLOW_DATA_ROAMING, // Not set during CTS
UserManager.DISALLOW_CAMERA_TOGGLE,
UserManager.DISALLOW_MICROPHONE_TOGGLE,
+ UserManager.DISALLOW_CHANGE_WIFI_STATE,
+ UserManager.DISALLOW_WIFI_TETHERING,
+ UserManager.DISALLOW_WIFI_DIRECT,
+ UserManager.DISALLOW_ADD_WIFI_CONFIG,
// PO can set them too, but when DO sets them, they're global.
UserManager.DISALLOW_ADJUST_VOLUME,
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 93b6817..1c672df 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
@@ -63,6 +63,11 @@
UserManager.DISALLOW_UNIFIED_PASSWORD,
UserManager.DISALLOW_CAMERA_TOGGLE,
UserManager.DISALLOW_MICROPHONE_TOGGLE,
+ UserManager.DISALLOW_CHANGE_WIFI_STATE,
+ UserManager.DISALLOW_WIFI_TETHERING,
+ UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
+ UserManager.DISALLOW_WIFI_DIRECT,
+ UserManager.DISALLOW_ADD_WIFI_CONFIG
};
public static final String[] DISALLOWED = new String[] {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
index 3863d90..47fd748 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
@@ -49,6 +49,8 @@
android:resource="@xml/device_admin"/>
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
+ <!-- TODO(b/176993670): remove WRAPPED_MANAGER_CALL if DpmWrapper goes away -->
+ <action android:name="com.android.bedstead.dpmwrapper.action.WRAPPED_MANAGER_CALL"/>
</intent-filter>
</receiver>
<receiver android:name="com.android.cts.deviceowner.CreateAndManageUserTest$SecondaryUserAdminReceiver"
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp b/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp
index a515d6b..584da16 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp
@@ -35,6 +35,7 @@
"testng",
"androidx.legacy_legacy-support-v4",
"devicepolicy-deviceside-common",
+ "permission-test-util-lib",
],
min_sdk_version: "27",
// tag this module as a cts test artifact
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index 3b8b877..0a049c9 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -42,6 +42,7 @@
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
<uses-permission android:name="android.permission.REQUEST_PASSWORD_COMPLEXITY"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application android:largeHeap="true"
android:testOnly="true">
@@ -205,6 +206,7 @@
<intent-filter>
<action android:name="com.android.cts.managedprofile.WIPE_DATA"/>
<action android:name="com.android.cts.managedprofile.WIPE_DATA_WITH_REASON"/>
+ <action android:name="com.android.cts.managedprofile.WIPE_DATA_WITHOUT_REASON"/>
</intent-filter>
</receiver>
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
index c24d01c..533b322 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
@@ -177,11 +177,6 @@
assertThat(actualMaximumTimeToLock).isEqualTo(maximumTimeToLock);
}
- public void testLockNow_onParent_isSupported() {
- mParentDevicePolicyManager.lockNow();
- // Will fail if a SecurityException is thrown.
- }
-
public void testSetAndGetKeyguardDisabledFeatures_onParent() {
mParentDevicePolicyManager.setKeyguardDisabledFeatures(
ADMIN_RECEIVER_COMPONENT, KEYGUARD_DISABLE_TRUST_AGENTS);
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/NotificationListenerTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/NotificationListenerTest.java
index b53bd03..1f519b3 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/NotificationListenerTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/NotificationListenerTest.java
@@ -15,6 +15,8 @@
*/
package com.android.cts.managedprofile;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+
import static com.google.common.truth.Truth.assertThat;
import android.app.admin.DevicePolicyManager;
@@ -24,6 +26,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
+import android.permission.cts.PermissionUtils;
import android.support.test.uiautomator.UiDevice;
import android.util.Log;
@@ -53,8 +56,8 @@
private static final String PARAM_PROFILE_ID = "profile-id";
- static final String SENDER_COMPONENT =
- "com.android.cts.managedprofiletests.notificationsender/.SendNotification";
+ static final String SENDER_PACKAGE = "com.android.cts.managedprofiletests.notificationsender";
+ static final String SENDER_COMPONENT = SENDER_PACKAGE + "/.SendNotification";
private final LocalBroadcastReceiver mReceiver = new LocalBroadcastReceiver();
private Context mContext;
@@ -68,6 +71,7 @@
mDpm = mContext.getSystemService(DevicePolicyManager.class);
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
mProfileUserId = getParam(InstrumentationRegistry.getArguments(), PARAM_PROFILE_ID);
+ PermissionUtils.grantPermission(SENDER_PACKAGE, POST_NOTIFICATIONS);
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_NOTIFICATION_POSTED);
filter.addAction(ACTION_NOTIFICATION_REMOVED);
@@ -77,6 +81,7 @@
@After
public void tearDown() throws Exception {
+ PermissionUtils.revokePermission(SENDER_PACKAGE, POST_NOTIFICATIONS);
LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mReceiver);
toggleNotificationListener(false);
}
diff --git a/hostsidetests/devicepolicy/app/NotificationSender/AndroidManifest.xml b/hostsidetests/devicepolicy/app/NotificationSender/AndroidManifest.xml
index 0f5ac6d..4417c5e 100644
--- a/hostsidetests/devicepolicy/app/NotificationSender/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/NotificationSender/AndroidManifest.xml
@@ -18,6 +18,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.managedprofiletests.notificationsender">
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+
<application>
<activity android:name=".SendNotification"
android:exported="true">
diff --git a/hostsidetests/devicepolicy/app/SeparateProfileChallenge/Android.bp b/hostsidetests/devicepolicy/app/SeparateProfileChallenge/Android.bp
deleted file mode 100644
index 3ac997b..0000000
--- a/hostsidetests/devicepolicy/app/SeparateProfileChallenge/Android.bp
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
- name: "CtsSeparateProfileChallengeApp",
- defaults: ["cts_defaults"],
- platform_apis: true,
- min_sdk_version: "27",
- srcs: ["src/**/*.java"],
- libs: [
- "android.test.runner.stubs",
- "junit",
- "android.test.base.stubs",
- ],
- static_libs: [
- "ctstestrunner-axt",
- "compatibility-device-util-axt",
- "ub-uiautomator",
- ],
- // tag this module as a cts test artifact
- test_suites: [
- "cts",
- "vts10",
- "general-tests",
- "sts",
- "mts",
- ],
-}
diff --git a/hostsidetests/devicepolicy/app/SeparateProfileChallenge/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SeparateProfileChallenge/AndroidManifest.xml
deleted file mode 100644
index 600b5c1..0000000
--- a/hostsidetests/devicepolicy/app/SeparateProfileChallenge/AndroidManifest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.separateprofilechallenge" >
-
- <uses-sdk android:minSdkVersion="27"/>
-
- <uses-permission android:name="WRITE_SECURE_SETTINGS"/>
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.cts.separateprofilechallenge"
- android:label="Separate Profile Challenge Permission CTS tests"/>
-</manifest>
diff --git a/hostsidetests/devicepolicy/app/SeparateProfileChallenge/src/com/android/cts/separateprofilechallenge/SeparateProfileChallengePermissionsTest.java b/hostsidetests/devicepolicy/app/SeparateProfileChallenge/src/com/android/cts/separateprofilechallenge/SeparateProfileChallengePermissionsTest.java
deleted file mode 100644
index e1ce7d6..0000000
--- a/hostsidetests/devicepolicy/app/SeparateProfileChallenge/src/com/android/cts/separateprofilechallenge/SeparateProfileChallengePermissionsTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.separateprofilechallenge;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.platform.test.annotations.AsbSecurityTest;
-import android.test.AndroidTestCase;
-
-import androidx.test.runner.AndroidJUnitRunner;
-
-import static org.junit.Assert.assertNotNull;
-
-public class SeparateProfileChallengePermissionsTest extends AndroidTestCase {
-
- public void testSeparateProfileChallengePermissions() throws Exception {
- DevicePolicyManager dpm = (DevicePolicyManager)
- mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- assertNotNull(dpm);
- try {
- dpm.isSeparateProfileChallengeAllowed(0); /* Try to use USER_SYSTEM */
- fail("The user must be system to call isSeparateProfileChallengeAllowed().");
- } catch (SecurityException ignore) {
- // That's what we want!
- } catch (NoSuchMethodError err) {
- // API unavailable - pass
- }
- }
-}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
index a6696aa..afa1a3e 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
@@ -87,12 +87,4 @@
public void testRunDeviceAdminTest() throws Exception {
runTests(getDeviceAdminApkPackage(), "DeviceAdminTest");
}
-
- @Test
- public void testResetPasswordDeprecated() throws Exception {
- assumeHasSecureLockScreenFeature();
-
- runTests(getDeviceAdminApkPackage(), "DeviceAdminPasswordTest",
- "testResetPasswordDeprecated");
- }
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index 96b8ee3..34b0642 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -88,6 +88,7 @@
private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
private static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen";
private static final String FEATURE_WIFI = "android.hardware.wifi";
+ private static final String FEATURE_WATCH = "android.hardware.type.watch";
//The maximum time to wait for user to be unlocked.
private static final long USER_UNLOCK_TIMEOUT_SEC = 30;
@@ -177,6 +178,9 @@
protected int mDeviceOwnerUserId;
protected int mPrimaryUserId;
+ /** Is test running on a watch */
+ protected boolean mIsWatch;
+
/** Record the initial user ID. */
protected int mInitialUserId;
@@ -206,6 +210,7 @@
mSupportsMultiUser = getMaxNumberOfUsersSupported() > 1;
mFixedPackages = getDevice().getInstalledPackageNames();
mBuildHelper = new CompatibilityBuildHelper(getBuild());
+ mIsWatch = hasDeviceFeature(FEATURE_WATCH);
String propertyValue = getDevice().getProperty("ro.product.first_api_level");
if (propertyValue != null && !propertyValue.isEmpty()) {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
index 2220d80..44ad152 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
@@ -112,10 +112,18 @@
if (!mHasManagedUserFeature) {
return;
}
- verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS, "testCanStartMainActivityByComponent");
- verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS, "testCanStartNonMainActivityByComponent");
- verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS, "testCannotStartNotExportedActivityByComponent");
- verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS, "testCannotStartActivityInOtherPackageByComponent");
+ verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+ "testCanStartMainActivityByComponent");
+ verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+ "testCanStartMainActivityByComponentWithStartCrossProfileActivitiesPermission");
+ verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+ "testCanStartNonMainActivityByComponent");
+ verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+ "testCanStartNonMainActivityByComponentWithStartCrossProfileActivitiesPermission");
+ verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+ "testCannotStartNotExportedActivityByComponent");
+ verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+ "testCannotStartActivityInOtherPackageByComponent");
}
@LargeTest
@@ -138,10 +146,16 @@
if (!mHasManagedUserFeature) {
return;
}
- verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS, "testCannotStartActivityByIntentWithNoPermissions");
- verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS, "testCanStartActivityByIntentWithInteractAcrossProfilesPermission");
- verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS, "testCanStartActivityByIntentWithInteractAcrossUsersPermission");
- verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS, "testCanStartActivityByIntentWithInteractAcrossUsersFullPermission");
+ verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+ "testCannotStartActivityByIntentWithNoPermissions");
+ verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+ "testCanStartActivityByIntentWithInteractAcrossProfilesPermission");
+ verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+ "testCannotStartActivityByIntentWithStartCrossProfileActivitiesPermission");
+ verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+ "testCanStartActivityByIntentWithInteractAcrossUsersPermission");
+ verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+ "testCanStartActivityByIntentWithInteractAcrossUsersFullPermission");
}
@LargeTest
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 8c8f543..7c9c5a8 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -609,62 +609,6 @@
executeDeviceTestMethod(".ApplicationHiddenTest", "testCannotHidePolicyExemptApps");
}
- // TODO(b/197491427): AccountManager support in TestApp
- @Test
- public void testAccountManagement_userRestrictionAddAccount() throws Exception {
- installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
- try {
- changeUserRestrictionOrFail(DISALLOW_MODIFY_ACCOUNTS, true, mUserId);
- executeAccountTest("testAddAccount_blocked");
- } finally {
- // Ensure we clear the user restriction
- changeUserRestrictionOrFail(DISALLOW_MODIFY_ACCOUNTS, false, mUserId);
- }
- executeAccountTest("testAddAccount_allowed");
- }
-
- // TODO(b/197491427): AccountManager support in TestApp
- @Test
- public void testAccountManagement_userRestrictionRemoveAccount() throws Exception {
- installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
- try {
- changeUserRestrictionOrFail(DISALLOW_MODIFY_ACCOUNTS, true, mUserId);
- executeAccountTest("testRemoveAccount_blocked");
- } finally {
- // Ensure we clear the user restriction
- changeUserRestrictionOrFail(DISALLOW_MODIFY_ACCOUNTS, false, mUserId);
- }
- executeAccountTest("testRemoveAccount_allowed");
- }
-
- // TODO(b/197491427): AccountManager support in TestApp
- @Test
- public void testAccountManagement_disabledAddAccount() throws Exception {
- installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
- try {
- changeAccountManagement(COMMAND_BLOCK_ACCOUNT_TYPE, ACCOUNT_TYPE, mUserId);
- executeAccountTest("testAddAccount_blocked");
- } finally {
- // Ensure we remove account management policies
- changeAccountManagement(COMMAND_UNBLOCK_ACCOUNT_TYPE, ACCOUNT_TYPE, mUserId);
- }
- executeAccountTest("testAddAccount_allowed");
- }
-
- // TODO(b/197491427): AccountManager support in TestApp
- @Test
- public void testAccountManagement_disabledRemoveAccount() throws Exception {
- installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
- try {
- changeAccountManagement(COMMAND_BLOCK_ACCOUNT_TYPE, ACCOUNT_TYPE, mUserId);
- executeAccountTest("testRemoveAccount_blocked");
- } finally {
- // Ensure we remove account management policies
- changeAccountManagement(COMMAND_UNBLOCK_ACCOUNT_TYPE, ACCOUNT_TYPE, mUserId);
- }
- executeAccountTest("testRemoveAccount_allowed");
- }
-
@Test
public void testDelegatedCertInstaller() throws Exception {
installAppAsUser(CERT_INSTALLER_APK, mUserId);
@@ -1093,14 +1037,6 @@
.build());
}
- /** Test for resetPassword for all devices. */
- @Test
- public void testResetPasswordDeprecated() throws Exception {
- assumeHasSecureLockScreenFeature();
-
- executeDeviceTestMethod(".ResetPasswordTest", "testResetPasswordDeprecated");
- }
-
@Test
public void testPasswordSufficientInitially() throws Exception {
executeDeviceTestClass(".PasswordSufficientInitiallyTest");
@@ -1386,16 +1322,6 @@
}
@Test
- public void testLockNowLogged() throws Exception {
- assertMetricsLogged(getDevice(), () -> {
- executeDeviceTestMethod(".DevicePolicyLoggingTest", "testLockNowLogged");
- }, new DevicePolicyEventWrapper.Builder(EventId.LOCK_NOW_VALUE)
- .setAdminPackageName(DEVICE_ADMIN_PKG)
- .setInt(0)
- .build());
- }
-
- @Test
public void testSetKeyguardDisabledFeaturesLogged() throws Exception {
assertMetricsLogged(getDevice(), () -> {
executeDeviceTestMethod(
@@ -1705,11 +1631,11 @@
// TODO(b/184175078): Migrate test to Bedstead when the infra is ready.
@Test
- public void testGetNearbyNotificationStreamingPolicy_getsNearbyStreamingDisabledAsDefault()
+ public void testGetNearbyNotificationStreamingPolicy_defaultToSameManagedAccountOnly()
throws Exception {
executeDeviceTestMethod(
".NearbyNotificationStreamingPolicyTest",
- "testGetNearbyNotificationStreamingPolicy_getsNearbyStreamingDisabledAsDefault");
+ "testGetNearbyNotificationStreamingPolicy_defaultToSameManagedAccountOnly");
}
// TODO(b/184175078): Migrate test to Bedstead when the infra is ready.
@@ -1722,11 +1648,11 @@
// TODO(b/184175078): Migrate test to Bedstead when the infra is ready.
@Test
- public void testGetNearbyAppStreamingPolicy_getsNearbyStreamingDisabledAsDefault()
+ public void testGetNearbyAppStreamingPolicy_defaultToSameManagedAccountOnly()
throws Exception {
executeDeviceTestMethod(
".NearbyAppStreamingPolicyTest",
- "testGetNearbyAppStreamingPolicy_getsNearbyStreamingDisabledAsDefault");
+ "testGetNearbyAppStreamingPolicy_defaultToSameManagedAccountOnly");
}
// TODO(b/184175078): Migrate test to Bedstead when the infra is ready.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
index 4422d6b..51d988c 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
@@ -61,13 +61,6 @@
"testPasswordConstraintsDoesntThrowAndPreservesValuesPreR");
}
- @Test
- public void testResetPasswordDeprecated() throws Exception {
- assumeHasSecureLockScreenFeature();
-
- executeDeviceTestMethod(".ResetPasswordTest", "testResetPasswordDeprecated");
- }
-
protected void executeDeviceTestClass(String className) throws Exception {
runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, mUserId);
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 17c2d48..116147d 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
import android.platform.test.annotations.AsbSecurityTest;
import android.platform.test.annotations.FlakyTest;
@@ -103,6 +104,7 @@
@Test
public void testProxyPacProxyTest() throws Exception {
+ assumeFalse("Test does not apply to WearOS", mIsWatch);
executeDeviceOwnerTest("proxy.PacProxyTest");
}
@@ -747,6 +749,7 @@
@Test
public void testSetStatusBarDisabledLogged() throws Exception {
+ assumeFalse("Test does not apply to WearOS", mIsWatch);
assertMetricsLogged(getDevice(), () -> {
executeDeviceTestMethod(".DevicePolicyLoggingTest", "testSetStatusBarDisabledLogged");
}, new DevicePolicyEventWrapper.Builder(EventId.SET_STATUS_BAR_DISABLED_VALUE)
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
index 0d11e0a..b11c459 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
@@ -38,15 +38,6 @@
@FlakyTest
@Test
- public void testLockNowWithKeyEviction() throws Exception {
- assumeHasFileBasedEncryptionAndSecureLockScreenFeatures();
-
- changeUserCredential(TEST_PASSWORD, null, mProfileUserId);
- lockProfile();
- }
-
- @FlakyTest
- @Test
public void testResetPasswordWithTokenBeforeUnlock() throws Exception {
assumeHasFileBasedEncryptionAndSecureLockScreenFeatures();
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index 75af1c8..7012f18 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -140,22 +140,6 @@
executeDeviceTestClass(".AdminConfiguredNetworksTest");
}
- @Override
- @Test
- @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "197909577",
- reason = "Will be migrated to new test infra")
- public void testAccountManagement_userRestrictionAddAccount() throws Exception {
- super.testAccountManagement_userRestrictionAddAccount();
- }
-
- @Override
- @Test
- @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "197909577",
- reason = "Will be migrated to new test infra")
- public void testAccountManagement_userRestrictionRemoveAccount() throws Exception {
- super.testAccountManagement_userRestrictionRemoveAccount();
- }
-
@Test
public void testSetTime() throws Exception {
assertMetricsLogged(getDevice(), () -> {
@@ -473,13 +457,6 @@
@Override
@Test
- @IgnoreOnHeadlessSystemUserMode(reason = "Headless system user doesn't have credentials")
- public void testResetPasswordDeprecated() throws Exception {
- super.testResetPasswordDeprecated();
- }
-
- @Override
- @Test
@IgnoreOnHeadlessSystemUserMode(reason = "Headless system user doesn't launch activities")
public void testCreateAdminSupportIntent() throws Exception {
super.testCreateAdminSupportIntent();
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/SeparateProfileChallengeTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/SeparateProfileChallengeTest.java
deleted file mode 100644
index 18e8ed9..0000000
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/SeparateProfileChallengeTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.devicepolicy;
-
-import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
-
-import android.platform.test.annotations.AsbSecurityTest;
-
-import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
-
-import org.junit.Test;
-
-/**
- * Host side tests for separate profile challenge permissions.
- * Run the CtsSeparateProfileChallengeApp device side test.
- */
-@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
-public class SeparateProfileChallengeTest extends BaseDevicePolicyTest {
- private static final String SEPARATE_PROFILE_PKG = "com.android.cts.separateprofilechallenge";
- private static final String SEPARATE_PROFILE_APK = "CtsSeparateProfileChallengeApp.apk";
- private static final String SEPARATE_PROFILE_TEST_CLASS =
- ".SeparateProfileChallengePermissionsTest";
- private String mPreviousHiddenApiPolicy = "0";
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
-
- setHiddenApiPolicyOn();
- }
-
- @Override
- public void tearDown() throws Exception {
-
- removeTestUsers();
- getDevice().uninstallPackage(SEPARATE_PROFILE_PKG);
- setHiddenApiPolicyPreviousOrOff();
- super.tearDown();
- }
-
- @Test
- @AsbSecurityTest(cveBugId = 128599668)
- public void testSeparateProfileChallengePermissions() throws Exception {
- assumeCanCreateOneManagedUser();
-
- // Create managed profile.
- final int profileUserId = createManagedProfile(mPrimaryUserId);
- // createManagedProfile doesn't start the user automatically.
- startUser(profileUserId);
- installAppAsUser(SEPARATE_PROFILE_APK, profileUserId);
- executeSeparateProfileChallengeTest(profileUserId);
- }
-
- protected void setHiddenApiPolicyOn() throws Exception {
- mPreviousHiddenApiPolicy = getDevice().executeShellCommand(
- "settings get global hidden_api_policy_p_apps");
- executeShellCommand("settings put global hidden_api_policy_p_apps 1");
- }
-
- protected void setHiddenApiPolicyPreviousOrOff() throws Exception {
- executeShellCommand("settings put global hidden_api_policy_p_apps "
- + mPreviousHiddenApiPolicy);
- }
-
- private void executeSeparateProfileChallengeTest(int userId) throws Exception {
- runDeviceTestsAsUser(SEPARATE_PROFILE_PKG, SEPARATE_PROFILE_TEST_CLASS, userId);
- }
-}
diff --git a/hostsidetests/edi/Android.bp b/hostsidetests/edi/Android.bp
index 1b32a4b..f3feb1a 100644
--- a/hostsidetests/edi/Android.bp
+++ b/hostsidetests/edi/Android.bp
@@ -24,6 +24,7 @@
"cts",
"gts",
"general-tests",
+ "tvts",
],
libs: [
"compatibility-host-util",
@@ -36,4 +37,6 @@
"modules-utils-build-testing",
"hamcrest-library",
],
+ data: [":SharedLibraryInfoTestApp"],
+ per_testcase_directory: true,
}
diff --git a/hostsidetests/graphics/gpumetrics/Android.bp b/hostsidetests/graphics/gpumetrics/Android.bp
new file mode 100644
index 0000000..b67305c
--- /dev/null
+++ b/hostsidetests/graphics/gpumetrics/Android.bp
@@ -0,0 +1,35 @@
+// Copyright 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+ name: "CtsGpuMetricsHostTestCases",
+ srcs: ["src/**/*.java"],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "compatibility-host-util",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ data: [
+ ":CtsFramestatsTestApp",
+ ],
+ per_testcase_directory: true,
+}
diff --git a/hostsidetests/graphics/gpumetrics/AndroidTest.xml b/hostsidetests/graphics/gpumetrics/AndroidTest.xml
new file mode 100644
index 0000000..2681fb9
--- /dev/null
+++ b/hostsidetests/graphics/gpumetrics/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="Runs CtsGpuMetricsHostTestCases">
+
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="graphics" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsFramestatsTestApp.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="jar" value="CtsGpuMetricsHostTestCases.jar" />
+ </test>
+</configuration>
diff --git a/hostsidetests/graphics/gpumetrics/TEST_MAPPING b/hostsidetests/graphics/gpumetrics/TEST_MAPPING
new file mode 100644
index 0000000..fec19ff
--- /dev/null
+++ b/hostsidetests/graphics/gpumetrics/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsGpuMetricsHostTestCases"
+ }
+ ]
+}
diff --git a/hostsidetests/graphics/gpumetrics/src/com/android/cts/graphics/GpuWorkDumpsysTest.java b/hostsidetests/graphics/gpumetrics/src/com/android/cts/graphics/GpuWorkDumpsysTest.java
new file mode 100644
index 0000000..0507669
--- /dev/null
+++ b/hostsidetests/graphics/gpumetrics/src/com/android/cts/graphics/GpuWorkDumpsysTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.graphics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class GpuWorkDumpsysTest extends BaseHostJUnit4Test {
+
+ private static final String DUMPSYS_COMMAND = "dumpsys gpu --gpuwork";
+ private static final String TEST_PKG = "com.android.cts.framestatstestapp";
+ private static final int PAUSE_MILLIS = 3000;
+
+ private CommandResult assertShellCommand(String command) throws DeviceNotAvailableException {
+ CommandResult commandResult = getDevice().executeShellV2Command(command);
+
+ // It must succeed.
+ assertEquals(
+ String.format("Failed shell command: %s", command),
+ CommandStatus.SUCCESS,
+ commandResult.getStatus());
+
+ return commandResult;
+ }
+
+ private long getTestAppUid() throws DeviceNotAvailableException {
+ int currentUser = getDevice().getCurrentUser();
+ CommandResult commandResult =
+ assertShellCommand(
+ String.format("cmd package list packages -U --user %d %s", currentUser, TEST_PKG));
+ String[] parts = commandResult.getStdout().split(":");
+ // Example output:
+ // package:com.android.cts.framestatstestapp uid:10183
+ assertTrue(
+ String.format("Unexpected output getting package uid:\n%s", commandResult.getStdout()),
+ parts.length > 2);
+
+ long appUid = Long.parseLong(parts[2].trim());
+ assertTrue(String.format("Unexpected app uid: %d", appUid), appUid > 10000);
+ return appUid;
+ }
+
+ @Test
+ public void testOutputFormat() throws Exception {
+ // Execute dumpsys command.
+ CommandResult commandResult = assertShellCommand(DUMPSYS_COMMAND);
+
+ // If the dumpsys command output indicates that the GPU information is not available then the
+ // test ends here.
+ assumeFalse(
+ "GPU time in state information was not available.",
+ commandResult.getStdout().contains("GPU time in state information is not available"));
+
+ // Turn screen on.
+ assertShellCommand("input keyevent KEYCODE_WAKEUP");
+ Thread.sleep(PAUSE_MILLIS);
+
+ // Skip lock screen.
+ assertShellCommand("wm dismiss-keyguard");
+ Thread.sleep(PAUSE_MILLIS);
+
+ // Start basic app.
+ assertShellCommand("am start -W -S " + TEST_PKG);
+ Thread.sleep(PAUSE_MILLIS);
+
+ // Get the UID of the test app.
+ long appUid = getTestAppUid();
+
+ // Execute dumpsys command again.
+ commandResult = assertShellCommand(DUMPSYS_COMMAND);
+
+ LogUtil.CLog.i("dumpsys output:\n%s", commandResult.getStdout());
+
+ String[] lines = commandResult.getStdout().trim().split("\n");
+ int i = 0;
+ for (; i < lines.length; ++i) {
+ if (lines[i].startsWith("uid/freq: 0MHz")) {
+ break;
+ }
+ }
+ assertTrue("Could not find uid/freq header in output", i < lines.length);
+
+ Pattern uidInfoPattern = Pattern.compile(String.format("(%d): \\d+", appUid));
+ for (; i < lines.length; ++i) {
+ Matcher matcher = uidInfoPattern.matcher(lines[i]);
+ if (!matcher.lookingAt()) {
+ continue;
+ }
+ if (appUid == Long.parseLong(matcher.group(1))) {
+ break;
+ }
+ }
+ assertTrue(String.format("Could not find UID %d in output", appUid), i < lines.length);
+ }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
index 2cce86d..cf1c432 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
@@ -162,10 +162,10 @@
String line;
String pattern =
"(.*?)"
- + "(mAddress: )"
+ + "(mDeviceInfo:)(.*)(logical_address: )"
+ "(?<"
+ "logicalAddress"
- + ">\\p{Digit}{1,2})"
+ + ">0x\\p{XDigit}{2})"
+ "(.*?)";
Pattern p = Pattern.compile(pattern);
try {
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java
index 72a2cf0..8c12d54 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java
@@ -20,18 +20,18 @@
import android.hdmicec.cts.BaseHdmiCecCtsTest;
import android.hdmicec.cts.CecMessage;
import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.error.CecClientWrapperException;
-import android.hdmicec.cts.error.ErrorCodes;
import android.hdmicec.cts.HdmiCecConstants;
import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.error.CecClientWrapperException;
+import android.hdmicec.cts.error.ErrorCodes;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import org.junit.Ignore;
import org.junit.Rule;
+import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
-import org.junit.Test;
/** HDMI CEC test to test audio return channel control (Section 11.2.17) */
@Ignore("b/162820841")
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
index 1b36a61..b36c673 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
@@ -21,9 +21,9 @@
import android.hdmicec.cts.CecMessage;
import android.hdmicec.cts.CecOperand;
import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
import android.hdmicec.cts.error.CecClientWrapperException;
import android.hdmicec.cts.error.ErrorCodes;
-import android.hdmicec.cts.LogicalAddress;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java
index 6c251a2..0ffc025 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java
@@ -21,20 +21,17 @@
import android.hdmicec.cts.BaseHdmiCecCtsTest;
import android.hdmicec.cts.CecMessage;
import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
import android.hdmicec.cts.HdmiCecConstants;
import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import org.junit.Ignore;
import org.junit.Rule;
+import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
-import org.junit.Test;
/** HDMI CEC test to verify logical address after device reboot (Section 10.2.5) */
@Ignore("b/162820841")
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java
index e27ae7e..3acab1c 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java
@@ -19,8 +19,6 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-import com.google.common.collect.Range;
-
import android.hdmicec.cts.AudioManagerHelper;
import android.hdmicec.cts.BaseHdmiCecCtsTest;
import android.hdmicec.cts.CecMessage;
@@ -30,12 +28,14 @@
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.google.common.collect.Range;
+
import org.junit.After;
import org.junit.Ignore;
import org.junit.Rule;
+import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
-import org.junit.Test;
import java.util.concurrent.TimeUnit;
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java
index 6541ca6..e8b1d8d 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java
@@ -36,7 +36,9 @@
import java.util.List;
import java.util.concurrent.TimeUnit;
-/** HDMI CEC test to verify the device handles standby correctly (Section 11.1.3, 11.2.3) */
+/**
+ * HDMI CEC test to verify the device handles standby correctly (Section 11.1.3, 11.2.3)
+ */
@RunWith(DeviceJUnit4ClassRunner.class)
public final class HdmiCecSystemStandbyTest extends BaseHdmiCecCtsTest {
@@ -50,7 +52,8 @@
@Rule
public RuleChain ruleChain =
- RuleChain.outerRule(CecRules.requiresCec(this))
+ RuleChain
+ .outerRule(CecRules.requiresCec(this))
.around(CecRules.requiresLeanback(this))
.around(hdmiCecClient);
@@ -83,7 +86,7 @@
WakeLockHelper.acquirePartialWakeLock(device);
hdmiCecClient.sendCecMessage(source, LogicalAddress.BROADCAST, CecOperand.STANDBY);
checkStandbyAndWakeUp();
- }
+ }
}
}
@@ -100,7 +103,7 @@
WakeLockHelper.acquirePartialWakeLock(device);
hdmiCecClient.sendCecMessage(source, CecOperand.STANDBY);
checkStandbyAndWakeUp();
- }
+ }
}
}
@@ -132,7 +135,7 @@
mLogicalAddresses.add(LogicalAddress.AUDIO_SYSTEM);
if (hasDeviceType(HdmiCecConstants.CEC_DEVICE_TYPE_TV)) {
- // Add logical addresses 13, 14 only for TV panel tests.
+ //Add logical addresses 13, 14 only for TV panel tests.
mLogicalAddresses.add(LogicalAddress.RESERVED_2);
mLogicalAddresses.add(LogicalAddress.SPECIFIC_USE);
}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecActiveTrackingTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecActiveTrackingTest.java
index 76c0688..949a3d5 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecActiveTrackingTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecActiveTrackingTest.java
@@ -34,14 +34,14 @@
import java.util.concurrent.TimeUnit;
/**
- * HDMI CEC tests verifying power status related messages of the device (CEC 2.0 CTS Section 7.6)
+ * HDMI CEC tests verifying the active tracking mechanism of the CEC network for Playback devices
*/
@RunWith(DeviceJUnit4ClassRunner.class)
public final class HdmiCecActiveTrackingTest extends BaseHdmiCecCtsTest {
// Delay to allow the DUT to poll all the non-local logical addresses (seconds)
private static final int POLLING_WAIT_TIME = 5;
- // Delay to wait for the HotplugDetectionAction to pass (seconds)
- private static final int HOTPLUG_WAIT_TIME = 60;
+ // Delay to wait for the HotplugDetectionAction to start (milliseconds)
+ private static final int HOTPLUG_WAIT_TIME = 60000;
public HdmiCecActiveTrackingTest() {
super(HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
@@ -65,13 +65,13 @@
/**
* Tests that the DUT removes a device from the network, when it doesn't answer to the polling
- * message sent by HotplugDetection action.
+ * message sent by HotplugDetectionAction.
*/
@Test
public void cect_RemoveDeviceFromNetwork() throws Exception {
// Wait for the device discovery action to pass.
TimeUnit.SECONDS.sleep(POLLING_WAIT_TIME);
- // Add Playback 2 in the network.
+ // Add an external playback device to the network.
int playback2PhysicalAddress = createUnusedPhysicalAddress(getDumpsysPhysicalAddress());
String formattedPhysicalAddress = CecMessage.formatParams(playback2PhysicalAddress,
HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH);
@@ -90,8 +90,11 @@
CecOperand.SET_OSD_NAME,
CecMessage.convertStringToHexParams(deviceName)
);
- // Wait for the first HotplugDetection action to pass.
- TimeUnit.SECONDS.sleep(HOTPLUG_WAIT_TIME);
+ // Wait for the first HotplugDetectionAction to start and leave enough time to poll all
+ // devices once.
+ hdmiCecClient.checkExpectedOutput(LogicalAddress.SPECIFIC_USE, CecOperand.POLL,
+ HOTPLUG_WAIT_TIME + HdmiCecConstants.DEVICE_WAIT_TIME_MS);
+ TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
String deviceList = getDeviceList();
assertThat(deviceList).doesNotContain(deviceName);
}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java
index afe78d4..459f260 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java
@@ -29,9 +29,9 @@
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import org.junit.Rule;
+import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
-import org.junit.Test;
/** HDMI CEC tests related to the device reporting the device OSD name (Section 11.2.11) */
@RunWith(DeviceJUnit4ClassRunner.class)
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
index e715f91..c7e0a9e 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
@@ -28,9 +28,9 @@
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import org.junit.Rule;
+import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
-import org.junit.Test;
import java.util.concurrent.TimeUnit;
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java
index 7f666e4..4826de5 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java
@@ -29,10 +29,9 @@
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import org.junit.Rule;
+import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
-import org.junit.Test;
-
import java.util.concurrent.TimeUnit;
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRoutingControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRoutingControlTest.java
index 1775f5e..d1b58ef 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRoutingControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRoutingControlTest.java
@@ -21,11 +21,11 @@
import android.hdmicec.cts.BaseHdmiCecCtsTest;
import android.hdmicec.cts.CecMessage;
import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.error.CecClientWrapperException;
-import android.hdmicec.cts.error.ErrorCodes;
import android.hdmicec.cts.HdmiCecConstants;
import android.hdmicec.cts.HdmiControlManagerUtility;
import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.error.CecClientWrapperException;
+import android.hdmicec.cts.error.ErrorCodes;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvStandbyTest.java
index 4efab29..0f7b3a9 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvStandbyTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvStandbyTest.java
@@ -16,6 +16,8 @@
package android.hdmicec.cts.tv;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.hdmicec.cts.BaseHdmiCecCtsTest;
import android.hdmicec.cts.CecOperand;
import android.hdmicec.cts.HdmiCecConstants;
@@ -30,12 +32,13 @@
import org.junit.runner.RunWith;
-import static com.google.common.truth.Truth.assertWithMessage;
-
/** HDMI CEC tests for system standby features (Section 11.1.3) */
@RunWith(DeviceJUnit4ClassRunner.class)
public class HdmiCecTvStandbyTest extends BaseHdmiCecCtsTest {
+ private static final String TV_SEND_STANDBY_ON_SLEEP = "tv_send_standby_on_sleep";
+ private static final String TV_SEND_STANDBY_ON_SLEEP_ENABLED = "1";
+
public HdmiCecTvStandbyTest() {
super(HdmiCecConstants.CEC_DEVICE_TYPE_TV);
}
@@ -47,9 +50,6 @@
.around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV))
.around(hdmiCecClient);
- private static final String HDMI_CONTROL_DEVICE_AUTO_OFF =
- "hdmi_control_auto_device_off_enabled";
-
/**
* Test 11.1.3-1
*
@@ -60,7 +60,8 @@
public void cect_11_1_3_1_BroadcastStandby() throws Exception {
ITestDevice device = getDevice();
device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
- boolean wasOn = setHdmiControlDeviceAutoOff(true);
+ String value = getSettingsValue(TV_SEND_STANDBY_ON_SLEEP);
+ setSettingsValue(TV_SEND_STANDBY_ON_SLEEP, TV_SEND_STANDBY_ON_SLEEP_ENABLED);
try {
sendDeviceToSleep();
hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST, CecOperand.STANDBY);
@@ -70,19 +71,7 @@
.isEqualTo("mWakefulness=Asleep");
} finally {
wakeUpDevice();
- setHdmiControlDeviceAutoOff(wasOn);
+ setSettingsValue(TV_SEND_STANDBY_ON_SLEEP, value);
}
}
-
- private boolean setHdmiControlDeviceAutoOff(boolean turnOn) throws Exception {
- ITestDevice device = getDevice();
- String val =
- device.executeShellCommand("settings get global " + HDMI_CONTROL_DEVICE_AUTO_OFF)
- .trim();
- String valToSet = turnOn ? "1" : "0";
- device.executeShellCommand(
- "settings put global " + HDMI_CONTROL_DEVICE_AUTO_OFF + " " + valToSet);
- device.executeShellCommand("settings get global " + HDMI_CONTROL_DEVICE_AUTO_OFF);
- return val.equals("1");
- }
}
diff --git a/hostsidetests/incident/OWNERS b/hostsidetests/incident/OWNERS
index 37c0932..60e88f9 100644
--- a/hostsidetests/incident/OWNERS
+++ b/hostsidetests/incident/OWNERS
@@ -1,10 +1,9 @@
-# Bug component: 329246
+# Bug component: 366902
jeffreyhuang@google.com
-joeo@google.com
jreck@google.com
-kwekua@google.com
+jtnguyen@google.com
muhammadq@google.com
-ruchirr@google.com
+sharaienko@google.com
singhtejinder@google.com
tsaichristine@google.com
yamasani@google.com
diff --git a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
index a756004..e959abd 100644
--- a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
@@ -45,8 +45,6 @@
DiskStatsProtoTest.verifyDiskStatsServiceDumpProto(dump.getDiskstats(), filterLevel, getDevice());
- PackageIncidentTest.verifyPackageServiceDumpProto(dump.getPackage(), filterLevel);
-
PowerIncidentTest.verifyPowerManagerServiceDumpProto(dump.getPower(), filterLevel);
if (PrintProtoTest.supportsPrinting(getDevice())) {
diff --git a/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java
deleted file mode 100644
index 66137c1..0000000
--- a/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java
+++ /dev/null
@@ -1,124 +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.pm.PackageProto;
-import android.service.pm.PackageProto.UserInfoProto;
-import android.service.pm.PackageServiceDumpProto;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/** Test for "dumpsys package --proto" */
-public class PackageIncidentTest extends ProtoDumpTestCase {
- // Use the test apk from the BatteryStatsIncidentTest
- private static final String DEVICE_SIDE_TEST_APK = "CtsBatteryStatsApp.apk";
- private static final String DEVICE_SIDE_TEST_PACKAGE = "com.android.server.cts.device.batterystats";
-
- @Override
- protected void tearDown() throws Exception {
- getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-
- super.tearDown();
- }
-
- private static void assertPositive(String name, long value) {
- if (value > 0) return;
- fail(name + " expected to be positive, but was: " + value);
- }
-
- private static void assertNotNegative(String name, long value) {
- if (value >= 0) return;
- fail(name + " expected to be zero or positive, but was: " + value);
- }
-
- /** Parse the output of "dumpsys package --proto" and make sure the values are probable. */
- public void testPackageServiceDump() throws Exception {
- final long st = System.currentTimeMillis();
-
- installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
-
- // Find the package UID, version code, and version string.
- final Matcher matcher =
- execCommandAndFind(
- "dumpsys package " + DEVICE_SIDE_TEST_PACKAGE,
- "userId=(\\d+).*versionCode=(\\d+).*versionName=([^\\n]*)",
- Pattern.DOTALL);
- final int uid = Integer.parseInt(matcher.group(1));
- final int versionCode = Integer.parseInt(matcher.group(2));
- final String versionString = matcher.group(3).trim();
-
- final PackageServiceDumpProto dump =
- getDump(PackageServiceDumpProto.parser(), "dumpsys package --proto");
-
- PackageProto testPackage = null;
- for (PackageProto pkg : dump.getPackagesList()) {
- if (pkg.getName().equals(DEVICE_SIDE_TEST_PACKAGE)) {
- testPackage = pkg;
- break;
- }
- }
-
- assertNotNull(testPackage);
- assertEquals(testPackage.getName(), DEVICE_SIDE_TEST_PACKAGE);
- assertEquals(testPackage.getUid(), uid);
- assertEquals(testPackage.getVersionCode(), versionCode);
- assertEquals(testPackage.getVersionString(), versionString);
- assertPositive("install_time_ms", testPackage.getInstallTimeMs());
- assertEquals(testPackage.getInstallTimeMs(), testPackage.getUpdateTimeMs());
- assertEquals(testPackage.getSplits(0).getName(), "base");
- assertEquals(testPackage.getSplits(0).getRevisionCode(), 0);
- assertNotNull(testPackage.getUserPermissionsList());
-
- UserInfoProto testUser = testPackage.getUsers(0);
- assertEquals(testUser.getId(), 0);
- assertEquals(testUser.getInstallType(),
- PackageProto.UserInfoProto.InstallType.FULL_APP_INSTALL);
- assertFalse(testUser.getIsHidden());
- assertFalse(testUser.getIsLaunched());
- assertFalse(testUser.getEnabledState() == PackageProto.UserInfoProto
- .EnabledState.COMPONENT_ENABLED_STATE_DISABLED_USER);
-
- verifyPackageServiceDumpProto(dump, PRIVACY_NONE);
- }
-
- static void verifyPackageServiceDumpProto(PackageServiceDumpProto dump, final int filterLevel) throws Exception {
- assertNotNull(dump.getVerifierPackage().getName());
- assertNotNull(dump.getSharedLibraries(0).getName());
- if (dump.getSharedLibraries(0).getIsJar()) {
- assertNotNull(dump.getSharedLibraries(0).getPath());
- } else {
- assertNotNull(dump.getSharedLibraries(0).getApk());
- }
- assertNotNull(dump.getFeatures(0).getName());
-
- PackageServiceDumpProto.SharedUserProto systemUser = null;
- for (PackageServiceDumpProto.SharedUserProto user : dump.getSharedUsersList()) {
- if (user.getUid() == 1000) {
- systemUser = user;
- break;
- }
- }
- assertNotNull(systemUser);
- assertEquals("android.uid.system", systemUser.getName());
-
- if (filterLevel == PRIVACY_AUTO) {
- for (String msg : dump.getMessagesList()) {
- assertTrue(msg.isEmpty());
- }
- }
- }
-}
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/Android.bp b/hostsidetests/inputmethodservice/deviceside/devicetest/Android.bp
index 4ad7237..a3774b8 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/Android.bp
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/Android.bp
@@ -35,5 +35,5 @@
"general-tests",
],
sdk_version: "test_current",
- min_sdk_version: "19",
+ min_sdk_version: "28",
}
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml
index 58b9e70..4b5fa9c 100755
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml
@@ -19,13 +19,6 @@
package="android.inputmethodservice.cts.devicetest"
android:targetSandboxVersion="2">
- <!--
- TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
- latest OS behaviors.
- -->
- <uses-sdk android:minSdkVersion="28"
- android:targetSdkVersion="28"/>
-
<application android:label="CtsInputMethodServiceDeviceTests"
android:icon="@mipmap/ic_launcher"
android:allowBackup="false"
diff --git a/hostsidetests/inputmethodservice/deviceside/edittextapp/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/edittextapp/AndroidManifest.xml
index faebb9f..0888db3 100755
--- a/hostsidetests/inputmethodservice/deviceside/edittextapp/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/edittextapp/AndroidManifest.xml
@@ -18,8 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.inputmethodservice.cts.edittextapp" android:targetSandboxVersion="2">
- <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
-
<application
android:label="@string/app_name">
<activity
diff --git a/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml
index b0eaaa7..cd1174e 100755
--- a/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml
@@ -18,13 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.inputmethodservice.cts.ime1">
- <!--
- TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
- latest OS behaviors.
- -->
- <uses-sdk android:minSdkVersion="19"
- android:targetSdkVersion="25"/>
-
<application android:label="@string/ime_name"
android:allowBackup="false"
android:theme="@android:style/Theme.InputMethod">
diff --git a/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml
index 0168b8d..09305df 100755
--- a/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml
@@ -18,13 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.inputmethodservice.cts.ime2">
- <!--
- TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
- latest OS behaviors.
- -->
- <uses-sdk android:minSdkVersion="19"
- android:targetSdkVersion="25"/>
-
<application android:label="@string/ime_name"
android:allowBackup="false"
android:theme="@android:style/Theme.InputMethod">
diff --git a/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml
index a8b17e3..ce79f74 100755
--- a/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml
@@ -18,9 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.inputmethodservice.cts.provider">
- <uses-sdk android:minSdkVersion="26"
- android:targetSdkVersion="26"/>
-
<application android:label="CtsInputMethodServiceEventProvider">
<provider android:authorities="android.inputmethodservice.cts.provider"
android:name="android.inputmethodservice.cts.provider.EventProvider"
@@ -28,7 +25,7 @@
<receiver android:name="android.inputmethodservice.cts.receiver.EventReceiver"
android:exported="true">
<intent-filter>
- <action android:name="android.inputmethodservice.cts.action.IME_EVENT"/>
+ <action android:name="android.inputmethodservice.cts.action.DEVICE_EVENT"/>
</intent-filter>
</receiver>
</application>
diff --git a/hostsidetests/install/app/src/android/cts/install/InstallTest.java b/hostsidetests/install/app/src/android/cts/install/InstallTest.java
index 4d9d835..711df3d 100644
--- a/hostsidetests/install/app/src/android/cts/install/InstallTest.java
+++ b/hostsidetests/install/app/src/android/cts/install/InstallTest.java
@@ -104,15 +104,12 @@
@Test
public void assert_commitFailure_phase() {
Install install = getParameterizedInstall(VERSION_CODE_TARGET);
- if (mEnableRollback) {
- InstallUtils.commitExpectingFailure(IllegalArgumentException.class,
- "Non-staged APEX session doesn't support INSTALL_ENABLE_ROLLBACK", install);
- } else if (mInstallType.equals(INSTALL_TYPE.SINGLE_APEX)) {
+ if (mInstallType.equals(INSTALL_TYPE.SINGLE_APEX)) {
InstallUtils.commitExpectingFailure(AssertionError.class,
"does not support non-staged update", install);
} else {
- InstallUtils.commitExpectingFailure(AssertionError.class,
- "Non-staged multi package install of APEX and APK packages is not supported",
+ InstallUtils.commitExpectingFailure(IllegalStateException.class,
+ "Mix of APK and APEX is not supported for non-staged multi-package session",
install);
}
}
diff --git a/hostsidetests/locale/Android.bp b/hostsidetests/locale/Android.bp
new file mode 100644
index 0000000..910c1c1
--- /dev/null
+++ b/hostsidetests/locale/Android.bp
@@ -0,0 +1,42 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+ name: "CtsLocaleManagerHostTestCases",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.java"],
+ // tag this module as a cts test artifact
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "compatibility-host-util",
+ "host-libprotobuf-java-full",
+ "platformprotos",
+ ],
+ static_libs: [
+ "cts-statsd-atom-host-test-utils",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ data: [
+ ":CtsLocaleManagerTestDeviceApp",
+ ":CtsAtomTestApp",
+ ],
+}
diff --git a/hostsidetests/locale/AndroidTest.xml b/hostsidetests/locale/AndroidTest.xml
new file mode 100644
index 0000000..64a0426
--- /dev/null
+++ b/hostsidetests/locale/AndroidTest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS LocaleManager host test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <!-- Module cannot run in secondary user because these tests require a device restart which
+ automatically switches back to the primary user. Switching to the secondary user within the
+ test still causes the user to remain in LOCKED state which means the tests don't work.
+
+ Also note that most of our API test cases are in our instrumentation CTS suite at
+ cts/tests/framework/base/locale, which does in fact work across secondary users.
+ -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsLocaleManagerTestDeviceApp.apk" />
+ <option name="test-file-name" value="CtsAtomTestApp.apk" />
+ </target_preparer>
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsLocaleManagerHostTestCases.jar" />
+ </test>
+</configuration>
diff --git a/hostsidetests/locale/OWNERS b/hostsidetests/locale/OWNERS
new file mode 100644
index 0000000..9d41159
--- /dev/null
+++ b/hostsidetests/locale/OWNERS
@@ -0,0 +1,2 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=1082762&template=1601534
+include /tests/framework/base/locale/OWNERS
diff --git a/hostsidetests/locale/TEST_MAPPING b/hostsidetests/locale/TEST_MAPPING
new file mode 100644
index 0000000..60a24a4
--- /dev/null
+++ b/hostsidetests/locale/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsLocaleManagerHostTestCases"
+ }
+ ]
+}
diff --git a/hostsidetests/locale/app/Android.bp b/hostsidetests/locale/app/Android.bp
new file mode 100644
index 0000000..d8ed825
--- /dev/null
+++ b/hostsidetests/locale/app/Android.bp
@@ -0,0 +1,24 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsLocaleManagerTestDeviceApp",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.java"],
+ sdk_version: "current",
+}
diff --git a/hostsidetests/locale/app/AndroidManifest.xml b/hostsidetests/locale/app/AndroidManifest.xml
new file mode 100755
index 0000000..cf50f13
--- /dev/null
+++ b/hostsidetests/locale/app/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.localemanager.app">
+
+ <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/hostsidetests/locale/app/src/android/localemanager/app/MainActivity.java b/hostsidetests/locale/app/src/android/localemanager/app/MainActivity.java
new file mode 100644
index 0000000..54a0210
--- /dev/null
+++ b/hostsidetests/locale/app/src/android/localemanager/app/MainActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.localemanager.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * A simple activity which doesn't do anything interesting, but needs to be installed so that we can
+ * set its app-specific locales.
+ */
+public class MainActivity extends Activity {
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ }
+
+}
diff --git a/hostsidetests/locale/atom/app/Android.bp b/hostsidetests/locale/atom/app/Android.bp
new file mode 100644
index 0000000..b14b97be
--- /dev/null
+++ b/hostsidetests/locale/atom/app/Android.bp
@@ -0,0 +1,24 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAtomTestApp",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.java"],
+ sdk_version: "system_current",
+}
diff --git a/hostsidetests/locale/atom/app/AndroidManifest.xml b/hostsidetests/locale/atom/app/AndroidManifest.xml
new file mode 100755
index 0000000..ec95e96
--- /dev/null
+++ b/hostsidetests/locale/atom/app/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.localemanager.atom.app">
+
+ <application>
+ <activity android:name=".ActivityForSettingLocalesOfAnotherApp"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".ActivityForNullCheckForInputLocales"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".ActivityForNullCheckForInputPackageName"
+ 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/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForNullCheckForInputLocales.java b/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForNullCheckForInputLocales.java
new file mode 100644
index 0000000..2908ec9
--- /dev/null
+++ b/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForNullCheckForInputLocales.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.localemanager.atom.app;
+
+import android.app.Activity;
+import android.app.LocaleManager;
+import android.os.Bundle;
+
+/**
+ * Activity which tries to apply null locales on the application when invoked.
+ */
+public class ActivityForNullCheckForInputLocales extends Activity {
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ callSetApplicationLocalesWithNullLocales();
+ }
+
+ public void callSetApplicationLocalesWithNullLocales() {
+ // This function is to verify that the service throws an exception when null target
+ // packageName is passed, and this scenario is logged with a failure in the
+ // ApplicationLocalesChangedAtom.
+ LocaleManager mLocaleManager = this.getSystemService(LocaleManager.class);
+ try {
+ mLocaleManager.setApplicationLocales(null);
+ } catch (NullPointerException expected) {
+ }
+ }
+}
diff --git a/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForNullCheckForInputPackageName.java b/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForNullCheckForInputPackageName.java
new file mode 100644
index 0000000..8852754
--- /dev/null
+++ b/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForNullCheckForInputPackageName.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.localemanager.atom.app;
+
+import android.app.Activity;
+import android.app.LocaleManager;
+import android.os.Bundle;
+import android.os.LocaleList;
+
+/**
+ * Activity which tries to call setApplicationLocales with null packageName when invoked.
+ */
+public class ActivityForNullCheckForInputPackageName extends Activity {
+ private static final String DEFAULT_LANGUAGE_TAGS = "hi-IN,de-DE";
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ callSetApplicationLocalesWithNullPackage();
+ }
+
+ public void callSetApplicationLocalesWithNullPackage() {
+ // This function is to verify that the service throws an exception when null target
+ // packageName is passed, and this scenario is logged with a failure in the
+ // ApplicationLocalesChangedAtom.
+ LocaleManager mLocaleManager = this.getSystemService(LocaleManager.class);
+ try {
+ mLocaleManager.setApplicationLocales(null,
+ LocaleList.forLanguageTags(DEFAULT_LANGUAGE_TAGS));
+ } catch (NullPointerException expected) {
+ }
+ }
+}
diff --git a/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForSettingLocalesOfAnotherApp.java b/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForSettingLocalesOfAnotherApp.java
new file mode 100644
index 0000000..8f4c2de
--- /dev/null
+++ b/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForSettingLocalesOfAnotherApp.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.localemanager.atom.app;
+
+import android.app.Activity;
+import android.app.LocaleManager;
+import android.os.Bundle;
+import android.os.LocaleList;
+
+/**
+ * Activity which calls setApplicationLocales() for another application when invoked.
+ */
+public class ActivityForSettingLocalesOfAnotherApp extends Activity {
+ private static final String DEFAULT_LANGUAGE_TAGS = "hi-IN,de-DE";
+ private static final String TEST_APP_PACKAGE_NAME = "android.localemanager.app";
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setLocalesForOtherApplication();
+ }
+
+ public void setLocalesForOtherApplication() {
+ LocaleManager mLocaleManager = this.getSystemService(LocaleManager.class);
+ try {
+ mLocaleManager.setApplicationLocales(TEST_APP_PACKAGE_NAME,
+ LocaleList.forLanguageTags(DEFAULT_LANGUAGE_TAGS));
+ } catch (SecurityException expected) {
+ }
+ }
+}
diff --git a/hostsidetests/locale/src/android/localemanager/cts/ApplicationLocalesChangedAtomTests.java b/hostsidetests/locale/src/android/localemanager/cts/ApplicationLocalesChangedAtomTests.java
new file mode 100644
index 0000000..9124ab7
--- /dev/null
+++ b/hostsidetests/locale/src/android/localemanager/cts/ApplicationLocalesChangedAtomTests.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.localemanager.cts;
+
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.os.AtomsProto;
+import com.android.os.StatsLog;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+
+import java.util.List;
+
+public class ApplicationLocalesChangedAtomTests extends DeviceTestCase implements IBuildReceiver {
+ public static final String ACTIVITY_FOR_NULL_CHECK_FOR_INPUT_PACKAGE_NAME =
+ "ActivityForNullCheckForInputPackageName";
+ public static final String ACTIVITY_FOR_SETTING_LOCALES_OF_ANOTHER_APP =
+ "ActivityForSettingLocalesOfAnotherApp";
+ public static final String ACTIVITY_FOR_NULL_CHECK_FOR_INPUT_LOCALES =
+ "ActivityForNullCheckForInputLocales";
+ private int mShellUid;
+
+ private static final String INSTALLED_PACKAGE_NAME_APP1 = "android.localemanager.app";
+ private static final String INSTALLED_PACKAGE_NAME_APP2 = "android.localemanager.atom.app";
+ private static final String INVALID_PACKAGE_NAME = "invalid.package.name";
+
+ private static final String DEFAULT_LANGUAGE_TAGS = "hi-IN,de-DE";
+ private static final String DEFAULT_LANGUAGE_TAGS_2 = "hi-IN,es-ES";
+ private static final String EMPTY_LANGUAGE_TAGS = "";
+ private static final int INVALID_UID = -1;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ resetAppLocales();
+ ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ AtomsProto.Atom.APPLICATION_LOCALES_CHANGED_FIELD_NUMBER);
+
+ // This will be ROOT_UID if adb is running as root, SHELL_UID otherwise.
+ mShellUid = DeviceUtils.getHostUid(getDevice());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ super.tearDown();
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ }
+
+ public void testAtomLogging_newConfiguration_logsAtomSuccessfully()
+ throws Exception {
+ // executing API to change locales of the installed application, this should trigger an
+ // ApplicationLocalesChanged atom entry to be logged.
+ executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME_APP1, DEFAULT_LANGUAGE_TAGS);
+
+ // Retrieving logged metric entries and asserting if they are as expected.
+ List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+ assertEquals(1, data.size());
+ AtomsProto.ApplicationLocalesChanged result = data.get(0)
+ .getAtom().getApplicationLocalesChanged();
+ verifyAtomDetails(mShellUid,
+ DeviceUtils.getAppUid(getDevice(), INSTALLED_PACKAGE_NAME_APP1),
+ /* expectedPreviousLocales= */ "", DEFAULT_LANGUAGE_TAGS,
+ AtomsProto.ApplicationLocalesChanged.Status.CONFIG_COMMITTED, result);
+
+ // executing API to change locales of the installed application, this should trigger an
+ // ApplicationLocalesChanged atom entry to be logged.
+ executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME_APP1, DEFAULT_LANGUAGE_TAGS_2);
+
+ List<StatsLog.EventMetricData> data2 = ReportUtils.getEventMetricDataList(getDevice());
+ assertEquals(1, data.size());
+ AtomsProto.ApplicationLocalesChanged result2 = data2.get(0)
+ .getAtom().getApplicationLocalesChanged();
+ verifyAtomDetails(mShellUid,
+ DeviceUtils.getAppUid(getDevice(), INSTALLED_PACKAGE_NAME_APP1),
+ DEFAULT_LANGUAGE_TAGS, DEFAULT_LANGUAGE_TAGS_2,
+ AtomsProto.ApplicationLocalesChanged.Status.CONFIG_COMMITTED, result2);
+ }
+
+ public void testAtomLogging_invalidPackage_logsAtomWithFailureInvalidPackageName()
+ throws Exception {
+ // calling setApplicationLocales() with an invalid package name.
+ executeSetApplicationLocalesCommand(INVALID_PACKAGE_NAME, DEFAULT_LANGUAGE_TAGS);
+
+ // retrieving logged metric data
+ List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+ // assert data was logged.
+ assertEquals(1, data.size());
+ AtomsProto.ApplicationLocalesChanged result = data.get(0)
+ .getAtom().getApplicationLocalesChanged();
+ // The input package name is invalid therefore the status should be:
+ // FAILURE_INVALID_TARGET_PACKAGE
+ verifyAtomDetails(mShellUid,
+ INVALID_UID, /* expectedPreviousLocales= */ "", DEFAULT_LANGUAGE_TAGS,
+ AtomsProto.ApplicationLocalesChanged.Status.FAILURE_INVALID_TARGET_PACKAGE,
+ result);
+ }
+
+ public void testAtomLogging_permissionAbsent_logsAtomWithFailurePermissionAbsent()
+ throws Exception {
+ // For the purpose of testing the failure case of "Permission Absent" we required one app
+ // (without the CHANGE_CONFIGURATION permission) to call
+ // LocaleManager#setApplicationLocales() for another application so that this call fails in
+ // a Security Exception. To replicate this scenario, SetApplicationLocales() was called
+ // from the MainActivity of app2, attempting to change locales of app1. When
+ // app2/MainActivity is invoked this failure test case gets recorded.
+ invokeActivityInApp2AndDestroyIt(ACTIVITY_FOR_SETTING_LOCALES_OF_ANOTHER_APP);
+
+ List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+ assertEquals(1, data.size());
+
+ AtomsProto.ApplicationLocalesChanged result = data.get(0)
+ .getAtom().getApplicationLocalesChanged();
+ verifyAtomDetails(DeviceUtils.getAppUid(getDevice(), INSTALLED_PACKAGE_NAME_APP2),
+ DeviceUtils.getAppUid(getDevice(), INSTALLED_PACKAGE_NAME_APP1),
+ /* expectedPreviousLocales= */ "", DEFAULT_LANGUAGE_TAGS,
+ AtomsProto.ApplicationLocalesChanged.Status.FAILURE_PERMISSION_ABSENT, result);
+ }
+
+ public void testAtomLogging_inputLocalesNull_logsAtomWithFailure()
+ throws Exception {
+ // For the purpose of testing the failure case of "null locales" we need an application
+ // to call setApplicationLocales() with null locales as input. To replicate this
+ // scenario, SetApplicationLocales() was called indirectly
+ // from the onCreate() of app2.ActivityForNullCheckForInputLocales with null input locales.
+ invokeActivityInApp2AndDestroyIt(ACTIVITY_FOR_NULL_CHECK_FOR_INPUT_LOCALES);
+
+ List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+ assertEquals(1, data.size());
+
+ AtomsProto.ApplicationLocalesChanged result = data.get(0)
+ .getAtom().getApplicationLocalesChanged();
+ verifyAtomDetails(DeviceUtils.getAppUid(getDevice(), INSTALLED_PACKAGE_NAME_APP2),
+ INVALID_UID, /* expectedPreviousLocales= */ "",
+ /* expectedNewLocales= */ "",
+ AtomsProto.ApplicationLocalesChanged.Status.STATUS_UNSPECIFIED, result);
+ }
+
+ public void testAtomLogging_nullPackageName_logsAtomWithFailure()
+ throws Exception {
+ // For the purpose of testing the failure case of "null PackageName" we need one application
+ // to call setApplicationLocales() with null packageName as input. To replicate this
+ // scenario, SetApplicationLocales() was called indirectly
+ // from the onCreate() of app2.ActivityForNullCheckForInputPackageName with null input
+ // package.
+ invokeActivityInApp2AndDestroyIt(ACTIVITY_FOR_NULL_CHECK_FOR_INPUT_PACKAGE_NAME);
+
+ List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+ assertEquals(1, data.size());
+
+ AtomsProto.ApplicationLocalesChanged result = data.get(0)
+ .getAtom().getApplicationLocalesChanged();
+ verifyAtomDetails(DeviceUtils.getAppUid(getDevice(), INSTALLED_PACKAGE_NAME_APP2),
+ INVALID_UID, /* expectedPreviousLocales= */ "",
+ /* expectedNewLocales= */ "",
+ AtomsProto.ApplicationLocalesChanged.Status.STATUS_UNSPECIFIED, result);
+ }
+
+ public void testAtomLogging_noConfigChange_logsAtomWithConfigUncommitted()
+ throws Exception {
+ executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME_APP1, DEFAULT_LANGUAGE_TAGS);
+ // same command called twice to replicate the case of no commit as previous config is
+ // same as current requested.
+ executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME_APP1, DEFAULT_LANGUAGE_TAGS);
+
+ // fetching metric data.
+ List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+ // assert: atom was logged twice
+ assertEquals(2, data.size());
+
+ // assert: expected config for the first call
+ AtomsProto.ApplicationLocalesChanged result1 = data.get(0)
+ .getAtom().getApplicationLocalesChanged();
+ verifyAtomDetails(mShellUid,
+ DeviceUtils.getAppUid(getDevice(), INSTALLED_PACKAGE_NAME_APP1),
+ /* expectedPreviousLocales= */"", DEFAULT_LANGUAGE_TAGS,
+ AtomsProto.ApplicationLocalesChanged.Status.CONFIG_COMMITTED, result1);
+
+ // assert: expected config for the second call
+ AtomsProto.ApplicationLocalesChanged result2 = data.get(1)
+ .getAtom().getApplicationLocalesChanged();
+
+ // previous locales are same as new one, therefore status should be: CONFIG_UNCOMMITTED
+ verifyAtomDetails(mShellUid,
+ DeviceUtils.getAppUid(getDevice(), INSTALLED_PACKAGE_NAME_APP1),
+ DEFAULT_LANGUAGE_TAGS, DEFAULT_LANGUAGE_TAGS,
+ AtomsProto.ApplicationLocalesChanged.Status.CONFIG_UNCOMMITTED, result2);
+ }
+
+ private void resetAppLocales() throws Exception {
+ executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME_APP1, EMPTY_LANGUAGE_TAGS);
+ executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME_APP2, EMPTY_LANGUAGE_TAGS);
+ }
+
+
+ private void executeSetApplicationLocalesCommand(String packageName, String languageTags)
+ throws Exception {
+ getDevice().executeShellCommand(
+ String.format(
+ "cmd locale set-app-locales %s --user 0 --locales %s",
+ packageName,
+ languageTags
+ )
+ );
+ }
+
+ private void verifyAtomDetails(int expectedCallingUid, int expectedTargetUid,
+ String expectedPreviousLocales, String expectedNewLocales,
+ AtomsProto.ApplicationLocalesChanged.Status expectedStatus,
+ AtomsProto.ApplicationLocalesChanged result) {
+ assertEquals(expectedCallingUid, result.getCallingUid());
+ assertEquals(expectedTargetUid, result.getTargetUid());
+ assertEquals(expectedPreviousLocales, result.getPrevLocales());
+ assertEquals(expectedNewLocales, result.getNewLocales());
+ assertEquals(expectedStatus, result.getStatus());
+ }
+
+ private void invokeActivityInApp2AndDestroyIt(String activityName) {
+ String activity = INSTALLED_PACKAGE_NAME_APP2 + "/." + activityName;
+ try {
+ // launch the activity
+ getDevice().executeShellCommand(
+ String.format(
+ "am start -W %s ",
+ activity
+ )
+ );
+ } catch (Exception e) {
+ // DO nothing.
+ }
+ // destroy the app.
+ try {
+ // force stop the application
+ getDevice().executeShellCommand(
+ String.format(
+ "am kill -W %s", INSTALLED_PACKAGE_NAME_APP2
+ )
+ );
+ } catch (Exception e) {
+ // DO nothing.
+ }
+ }
+}
diff --git a/hostsidetests/locale/src/android/localemanager/cts/LocaleManagerHostTest.java b/hostsidetests/locale/src/android/localemanager/cts/LocaleManagerHostTest.java
new file mode 100644
index 0000000..1422195
--- /dev/null
+++ b/hostsidetests/locale/src/android/localemanager/cts/LocaleManagerHostTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.localemanager.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IDeviceTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test to check that {@link android.app.LocaleManager} APIs persist across device restarts.
+ *
+ * <p>When this test builds, it also builds {@link android.localemanager.app.MainActivity} into an
+ * APK which is then installed at runtime. The Activity does not do anything interesting, but we
+ * need it to be installed so that we can set an app-specific locale for it's package.
+ *
+ * <p><b>Note:</b> A more comprehensive set of CTS cases are included in the instrumentation suite
+ * at {@link android.localemanager.cts.LocaleManagerTests}.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class LocaleManagerHostTest implements IDeviceTest {
+
+ private static final String TAG = LocaleManagerHostTest.class.getSimpleName();
+
+ private static final String INSTALLED_PACKAGE_NAME = "android.localemanager.app";
+
+ private static final String DEFAULT_LANGUAGE_TAGS = "hi-IN,de-DE";
+ private static final String EMPTY_LANGUAGE_TAGS = "";
+
+ private static final String GET_APP_LOCALES_SHELL_OUTPUT_FORMAT =
+ "Locales for %s for user 0 are [%s]\n";
+ private static final String DEFAULT_LANGUAGE_TAGS_GET_APP_LOCALES_SHELL_OUTPUT =
+ String.format(GET_APP_LOCALES_SHELL_OUTPUT_FORMAT,
+ INSTALLED_PACKAGE_NAME, DEFAULT_LANGUAGE_TAGS);
+ private static final String EMPTY_LANGUAGE_TAGS_GET_APP_LOCALES_SHELL_OUTPUT =
+ String.format(GET_APP_LOCALES_SHELL_OUTPUT_FORMAT,
+ INSTALLED_PACKAGE_NAME, EMPTY_LANGUAGE_TAGS);
+
+ private ITestDevice mDevice;
+
+ @Override
+ public void setDevice(ITestDevice device) {
+ mDevice = device;
+ }
+
+ @Override
+ public ITestDevice getDevice() {
+ return mDevice;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ ITestDevice device = getDevice();
+ assertNotNull("Device not set", device);
+ resetAppLocales();
+ }
+
+ @Test
+ public void testSetApplicationLocale_nonEmptyLocales_persistsAcrossReboots() throws Exception {
+ executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME, DEFAULT_LANGUAGE_TAGS);
+ String appLocalesBeforeRestart =
+ executeGetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME);
+ assertEquals(DEFAULT_LANGUAGE_TAGS_GET_APP_LOCALES_SHELL_OUTPUT, appLocalesBeforeRestart);
+
+ restartDeviceAndWaitUntilReady();
+
+ // Verify locales still equal after restart
+ String appLocalesAfterRestart = executeGetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME);
+ assertEquals(DEFAULT_LANGUAGE_TAGS_GET_APP_LOCALES_SHELL_OUTPUT, appLocalesAfterRestart);
+ }
+
+ @Test
+ public void testSetApplicationLocale_emptyLocales_persistsAcrossReboots() throws Exception {
+ // set some application locales to start with
+ executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME, DEFAULT_LANGUAGE_TAGS);
+ String appLocalesBase = executeGetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME);
+ assertEquals(DEFAULT_LANGUAGE_TAGS_GET_APP_LOCALES_SHELL_OUTPUT, appLocalesBase);
+
+ // reset the application locales to empty
+ executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME, EMPTY_LANGUAGE_TAGS);
+ String appLocalesBeforeRestart =
+ executeGetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME);
+ assertEquals(EMPTY_LANGUAGE_TAGS_GET_APP_LOCALES_SHELL_OUTPUT, appLocalesBeforeRestart);
+
+ restartDeviceAndWaitUntilReady();
+ // Verify new empty locales persisted after restart
+ String appLocalesAfterRestart = executeGetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME);
+ assertEquals(EMPTY_LANGUAGE_TAGS_GET_APP_LOCALES_SHELL_OUTPUT, appLocalesAfterRestart);
+ }
+
+ private void restartDeviceAndWaitUntilReady() throws Exception {
+ // Flush pending writes before rebooting device
+ getDevice().executeShellCommand("am write");
+ getDevice().reboot();
+ }
+
+
+ private void resetAppLocales() throws Exception {
+ executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME, EMPTY_LANGUAGE_TAGS);
+ }
+
+
+ private void executeSetApplicationLocalesCommand(String packageName, String languageTags)
+ throws Exception {
+ getDevice().executeShellCommand(
+ String.format(
+ "cmd locale set-app-locales %s --user 0 --locales %s",
+ packageName,
+ languageTags
+ )
+ );
+ }
+
+ private String executeGetApplicationLocalesCommand(String packageName) throws Exception {
+ return getDevice().executeShellCommand(
+ String.format(
+ "cmd locale get-app-locales %s --user 0",
+ packageName
+ )
+ );
+ }
+
+}
diff --git a/hostsidetests/media/OWNERS b/hostsidetests/media/OWNERS
index e72446f..890f7d2 100644
--- a/hostsidetests/media/OWNERS
+++ b/hostsidetests/media/OWNERS
@@ -1,8 +1,12 @@
# Bug component: 1344
elaurent@google.com
+gyumin@google.com
+hdmoon@google.com
+jaewan@google.com
+jinpark@google.com
+klhyun@google.com
lajos@google.com
sungsoo@google.com
-jaewan@google.com
# go/android-fwk-media-solutions for info on areas of ownership.
include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
diff --git a/hostsidetests/media/app/MediaExtractorTest/src/android/media/cts/MediaExtractorDeviceSideTest.java b/hostsidetests/media/app/MediaExtractorTest/src/android/media/cts/MediaExtractorDeviceSideTest.java
index 75a4c1e..026a1b1 100644
--- a/hostsidetests/media/app/MediaExtractorTest/src/android/media/cts/MediaExtractorDeviceSideTest.java
+++ b/hostsidetests/media/app/MediaExtractorTest/src/android/media/cts/MediaExtractorDeviceSideTest.java
@@ -56,8 +56,9 @@
InstrumentationRegistry.getInstrumentation().getContext().getAssets();
try (AssetFileDescriptor fileDescriptor = assetManager.openFd(SAMPLE_PATH)) {
mediaExtractor.setDataSource(fileDescriptor);
+ } finally {
+ mediaExtractor.release();
}
- mediaExtractor.release();
}
@Test
diff --git a/hostsidetests/media/src/android/media/cts/MediaExtractorHostSideTest.java b/hostsidetests/media/src/android/media/cts/MediaExtractorHostSideTest.java
index f0c8a9b..78b126c 100644
--- a/hostsidetests/media/src/android/media/cts/MediaExtractorHostSideTest.java
+++ b/hostsidetests/media/src/android/media/cts/MediaExtractorHostSideTest.java
@@ -149,7 +149,7 @@
}
/**
- * Returns all MediaParser reported metric events sorted by timestamp.
+ * Asserts that a single entry point has been reported by MediaMetrics and returns it.
*
* <p>Note: Calls {@link #getAndClearReportList()} to obtain the statsd report.
*/
diff --git a/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java b/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
index 02a49da..87285e8 100644
--- a/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
+++ b/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
@@ -29,9 +29,7 @@
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IBuildReceiver;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Base64;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -534,7 +532,7 @@
List<Set<Integer>> directionList = Arrays.asList(directionSet);
List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
- AtomTestUtils.assertStatesOccurred(directionList, data, 0,
+ AtomTestUtils.assertStatesOccurredInOrder(directionList, data, 0,
atom -> atom.getMediametricsAaudiostreamReported().getDirection().getNumber());
for (StatsLog.EventMetricData event : data) {
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/EphemeralTest.java b/hostsidetests/multiuser/src/android/host/multiuser/EphemeralTest.java
index a8a12af..91ac046 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/EphemeralTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/EphemeralTest.java
@@ -93,35 +93,37 @@
}
/**
- * Test to verify that {@link android.os.UserManager#removeUserOrSetEphemeral(int)} immediately
+ * Test to verify that
+ * {@link android.os.UserManager#removeUserWhenPossible(UserHandle, boolean)} immediately
* removes a user that isn't running.
- *
- * <p>Indirectly executed by means of the --set-ephemeral-if-in-use flag
+ * <p>
+ * Indirectly executed by means of the --set-ephemeral-if-in-use flag
*/
@Presubmit
@Test
- public void testRemoveUserOrSetEphemeral_nonRunningUserRemoved() throws Exception {
+ public void testRemoveUserWhenPossible_nonRunningUserRemoved() throws Exception {
final int userId = createUser();
- executeRemoveUserOrSetEphemeralAdbCommand(userId);
+ executeRemoveUserWhenPossibleAdbCommand(userId);
assertUserNotPresent(userId);
}
/**
- * Test to verify that {@link android.os.UserManager#removeUserOrSetEphemeral(int)} sets the
- * current user to ephemeral and removes the user after user switch.
- *
- * <p>Indirectly executed by means of the --set-ephemeral-if-in-use flag
+ * Test to verify that
+ * {@link android.os.UserManager#removeUserWhenPossible(UserHandle, boolean)} sets the current
+ * user to ephemeral and removes the user after user switch.
+ * <p>
+ * Indirectly executed by means of the --set-ephemeral-if-in-use flag
*/
@Presubmit
@Test
- public void testRemoveUserOrSetEphemeral_currentUserSetEphemeral_removeAfterSwitch()
+ public void testRemoveUserWhenPossible_currentUserSetEphemeral_removeAfterSwitch()
throws Exception {
final int userId = createUser();
assertSwitchToNewUser(userId);
- executeRemoveUserOrSetEphemeralAdbCommand(userId);
+ executeRemoveUserWhenPossibleAdbCommand(userId);
assertUserEphemeral(userId);
assertSwitchToUser(userId, mInitialUserId);
@@ -130,26 +132,27 @@
}
/**
- * Test to verify that {@link android.os.UserManager#removeUserOrSetEphemeral(int)} sets the
- * current user to ephemeral and removes that user after reboot.
- *
- * <p>Indirectly executed by means of the --set-ephemeral-if-in-use flag
+ * Test to verify that
+ * {@link android.os.UserManager#removeUserWhenPossible(UserHandle, boolean)} sets the current
+ * user to ephemeral and removes that user after reboot.
+ * <p>
+ * Indirectly executed by means of the --set-ephemeral-if-in-use flag
*/
@Presubmit
@Test
- public void testRemoveUserOrSetEphemeral_currentUserSetEphemeral_removeAfterReboot()
+ public void testRemoveUserWhenPossible_currentUserSetEphemeral_removeAfterReboot()
throws Exception {
final int userId = createUser();
assertSwitchToNewUser(userId);
- executeRemoveUserOrSetEphemeralAdbCommand(userId);
+ executeRemoveUserWhenPossibleAdbCommand(userId);
assertUserEphemeral(userId);
getDevice().reboot();
assertUserNotPresent(userId);
}
- private void executeRemoveUserOrSetEphemeralAdbCommand(int userId) throws Exception {
+ private void executeRemoveUserWhenPossibleAdbCommand(int userId) throws Exception {
getDevice().executeShellV2Command("pm remove-user --set-ephemeral-if-in-use " + userId);
}
diff --git a/hostsidetests/os/AndroidTest.xml b/hostsidetests/os/AndroidTest.xml
index 1eb77ac..d0a869c 100644
--- a/hostsidetests/os/AndroidTest.xml
+++ b/hostsidetests/os/AndroidTest.xml
@@ -20,6 +20,17 @@
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts/hostside/os" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsStaticSharedLibProviderApp1.apk->/data/local/tmp/cts/hostside/os/CtsStaticSharedLibProviderApp1.apk" />
+ <option name="push" value="CtsStaticSharedLibProviderApp2.apk->/data/local/tmp/cts/hostside/os/CtsStaticSharedLibProviderApp2.apk" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsDeviceOsTestApp.apk" />
diff --git a/hostsidetests/os/src/android/os/cts/InattentiveSleepTests.java b/hostsidetests/os/src/android/os/cts/InattentiveSleepTests.java
index e117d14..1e04774 100644
--- a/hostsidetests/os/src/android/os/cts/InattentiveSleepTests.java
+++ b/hostsidetests/os/src/android/os/cts/InattentiveSleepTests.java
@@ -40,15 +40,20 @@
@RunWith(DeviceJUnit4ClassRunner.class)
public class InattentiveSleepTests extends BaseHostJUnit4Test {
private static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
- private static final String PACKAGE_NAME = "android.os.inattentivesleeptests";
private static final String APK_NAME = "CtsInattentiveSleepTestApp.apk";
private static final long TIME_BEFORE_WARNING_MS = 1200L;
private static final String CMD_DUMPSYS_POWER = "dumpsys power --proto";
private static final String WARNING_WINDOW_TOKEN_TITLE = "InattentiveSleepWarning";
- private static final String CMD_START_APP_TEMPLATE =
- "am start -W -a android.intent.action.MAIN -p %s -c android.intent.category.LAUNCHER";
+ private static final String ACTIVITY_KEEP_SCREEN_ON = ".KeepScreenOnActivity";
+ private static final String SERVICE_PARTIAL_WAKE_LOCK = ".PartialWakeLockService";
+ private static final String CMD_START_ACTIVITY_TEMPLATE =
+ "am start -W android.os.inattentivesleeptests/%s";
+ private static final String CMD_START_FG_SERVICE_TEMPLATE =
+ "am start-foreground-service android.os.inattentivesleeptests/%s";
+ private static final String CMD_STOP_SERVICE_TEMPLATE =
+ "am stop-service android.os.inattentivesleeptests/%s";
private static final String CMD_GET_STAY_ON = "settings get global stay_on_while_plugged_in";
private static final String CMD_PUT_STAY_ON_TEMPLATE =
@@ -81,12 +86,20 @@
mWarningDurationConfig = getWarningDurationConfig();
mOriginalStayOnSetting = Long.parseLong(
mDevice.executeShellCommand(CMD_GET_STAY_ON).trim());
+
+ installPackage(APK_NAME);
+
+ // Prevent the device from suspending while screen is off
+ startPartialWakeLockService();
+
mDevice.executeShellCommand(CMD_DISABLE_STAY_ON);
setInattentiveSleepTimeout(TIME_BEFORE_WARNING_MS + mWarningDurationConfig);
}
@After
public void tearDown() throws Exception {
+ wakeUp();
+ stopPartialWakeLockService();
mDevice.executeShellCommand(
String.format(CMD_PUT_STAY_ON_TEMPLATE, mOriginalStayOnSetting));
mDevice.executeShellCommand(CMD_DELETE_TIMEOUT_SETTING);
@@ -101,9 +114,19 @@
mDevice.executeShellCommand(CMD_KEYEVENT_WAKEUP);
}
+ private void startPartialWakeLockService() throws Exception {
+ mDevice.executeShellCommand(
+ String.format(CMD_START_FG_SERVICE_TEMPLATE, SERVICE_PARTIAL_WAKE_LOCK));
+ }
+
+ private void stopPartialWakeLockService() throws Exception {
+ mDevice.executeShellCommand(
+ String.format(CMD_STOP_SERVICE_TEMPLATE, SERVICE_PARTIAL_WAKE_LOCK));
+ }
+
private void startKeepScreenOnActivity() throws Exception {
- installPackage(APK_NAME);
- mDevice.executeShellCommand(String.format(CMD_START_APP_TEMPLATE, PACKAGE_NAME));
+ mDevice.executeShellCommand(
+ String.format(CMD_START_ACTIVITY_TEMPLATE, ACTIVITY_KEEP_SCREEN_ON));
}
private void setInattentiveSleepTimeout(long timeoutMs) throws Exception {
diff --git a/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java b/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
index 360d8d7..39d6df0 100644
--- a/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
@@ -16,10 +16,16 @@
package android.os.cts;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.AppModeInstant;
+import android.platform.test.annotations.LargeTest;
+import android.platform.test.annotations.Presubmit;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.PollingCheck;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
import com.android.tradefed.build.IBuildInfo;
@@ -30,10 +36,16 @@
import com.android.tradefed.result.TestRunResult;
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
import java.io.FileNotFoundException;
+import java.util.Arrays;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+@Presubmit
public class StaticSharedLibsHostTests extends DeviceTestCase implements IBuildReceiver {
private static final String ANDROID_JUNIT_RUNNER_CLASS =
"androidx.test.runner.AndroidJUnitRunner";
@@ -43,6 +55,9 @@
private static final String STATIC_LIB_PROVIDER_RECURSIVE_PKG =
"android.os.lib.provider.recursive";
+ private static final String STATIC_LIB_PROVIDER_RECURSIVE_NAME = "foo.bar.lib.recursive";
+ private static final String STATIC_LIB_PROVIDER_NAME = "foo.bar.lib";
+
private static final String STATIC_LIB_PROVIDER1_APK = "CtsStaticSharedLibProviderApp1.apk";
private static final String STATIC_LIB_PROVIDER1_PKG = "android.os.lib.provider";
@@ -88,6 +103,15 @@
private static final String STATIC_LIB_NATIVE_CONSUMER_PKG
= "android.os.lib.consumer";
+ private static final String STATIC_LIB_TEST_APP_PKG = "android.os.lib.app";
+ private static final String STATIC_LIB_TEST_APP_CLASS_NAME = STATIC_LIB_TEST_APP_PKG
+ + ".StaticSharedLibsTests";
+
+ private static final String SETTING_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD =
+ "unused_static_shared_lib_min_cache_period";
+
+ private static final long DEFAULT_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(15);
+
private CompatibilityBuildHelper mBuildHelper;
private boolean mInstantMode = false;
@@ -683,8 +707,7 @@
@AppModeFull(reason = "Instant app cannot get package installer service")
public void testCannotSamegradeStaticSharedLibByInstaller() throws Exception {
- runDeviceTests("android.os.lib.app",
- "android.os.lib.app.StaticSharedLibsTests",
+ runDeviceTests(STATIC_LIB_TEST_APP_PKG, STATIC_LIB_TEST_APP_CLASS_NAME,
"testSamegradeStaticSharedLibFail");
}
@@ -720,6 +743,111 @@
}
}
+ @LargeTest
+ @AppModeFull
+ public void testPruneUnusedStaticSharedLibraries_reboot_fullMode()
+ throws Exception {
+ doTestPruneUnusedStaticSharedLibraries_reboot();
+ }
+
+ @LargeTest
+ @AppModeInstant
+ public void testPruneUnusedStaticSharedLibraries_reboot_instantMode()
+ throws Exception {
+ mInstantMode = true;
+ doTestPruneUnusedStaticSharedLibraries_reboot();
+ }
+
+ private void doTestPruneUnusedStaticSharedLibraries_reboot()
+ throws Exception {
+ getDevice().uninstallPackage(STATIC_LIB_CONSUMER3_PKG);
+ getDevice().uninstallPackage(STATIC_LIB_PROVIDER7_PKG);
+ getDevice().uninstallPackage(STATIC_LIB_PROVIDER_RECURSIVE_PKG);
+ try {
+ // Install an unused library
+ assertThat(install(STATIC_LIB_PROVIDER_RECURSIVE_APK)).isNull();
+ assertThat(checkLibrary(STATIC_LIB_PROVIDER_RECURSIVE_NAME)).isTrue();
+
+ // Install the client and the corresponding library
+ assertThat(install(STATIC_LIB_PROVIDER7_APK)).isNull();
+ assertThat(install(STATIC_LIB_CONSUMER3_APK)).isNull();
+ assertThat(checkLibrary(STATIC_LIB_PROVIDER_NAME)).isTrue();
+
+ // Disallow to cache static shared library
+ setGlobalSetting(SETTING_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD,
+ Integer.toString(0));
+
+ // TODO(205779832): There's a maximum two-seconds-delay before SettingsProvider persists
+ // the settings. Waits for 3 seconds before reboot the device to ensure the setting is
+ // persisted.
+ Thread.sleep(3_000);
+ getDevice().reboot();
+
+ // Waits for the uninstallation of the unused library to ensure the job has be executed
+ // correctly.
+ PollingCheck.check("Library " + STATIC_LIB_PROVIDER_RECURSIVE_NAME
+ + " should be uninstalled", DEFAULT_TIMEOUT_MILLIS,
+ () -> !checkLibrary(STATIC_LIB_PROVIDER_RECURSIVE_NAME));
+ assertWithMessage(
+ "Library " + STATIC_LIB_PROVIDER_NAME + " should not be uninstalled")
+ .that(checkLibrary(STATIC_LIB_PROVIDER_NAME)).isTrue();
+ } finally {
+ setGlobalSetting(SETTING_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD, null);
+ getDevice().uninstallPackage(STATIC_LIB_CONSUMER3_PKG);
+ getDevice().uninstallPackage(STATIC_LIB_PROVIDER7_PKG);
+ getDevice().uninstallPackage(STATIC_LIB_PROVIDER_RECURSIVE_PKG);
+ }
+ }
+
+ @LargeTest
+ @AppModeFull
+ public void testInstallStaticSharedLib_notKillDependentApp() throws Exception {
+ getDevice().uninstallPackage(STATIC_LIB_CONSUMER1_PKG);
+ getDevice().uninstallPackage(STATIC_LIB_PROVIDER1_PKG);
+ getDevice().uninstallPackage(STATIC_LIB_PROVIDER_RECURSIVE_PKG);
+ try {
+ // Install library dependency
+ assertNull(install(STATIC_LIB_PROVIDER_RECURSIVE_APK));
+ // Install the first library
+ assertNull(install(STATIC_LIB_PROVIDER1_APK));
+ // Install the client
+ assertNull(install(STATIC_LIB_CONSUMER1_APK));
+
+ // Bind the service in consumer1 app to verify that the app should not be killed when
+ // a new version static shared library installed.
+ runDeviceTests(STATIC_LIB_TEST_APP_PKG, STATIC_LIB_TEST_APP_CLASS_NAME,
+ "testInstallStaticSharedLib_notKillDependentApp");
+ } finally {
+ getDevice().uninstallPackage(STATIC_LIB_CONSUMER1_PKG);
+ getDevice().uninstallPackage(STATIC_LIB_PROVIDER1_PKG);
+ getDevice().uninstallPackage(STATIC_LIB_PROVIDER_RECURSIVE_PKG);
+ }
+ }
+
+ @AppModeFull
+ public void testSamegradeStaticSharedLib_killDependentApp() throws Exception {
+ getDevice().uninstallPackage(STATIC_LIB_CONSUMER1_PKG);
+ getDevice().uninstallPackage(STATIC_LIB_PROVIDER1_PKG);
+ getDevice().uninstallPackage(STATIC_LIB_PROVIDER_RECURSIVE_PKG);
+ try {
+ // Install library dependency
+ assertNull(install(STATIC_LIB_PROVIDER_RECURSIVE_APK));
+ // Install the first library
+ assertNull(install(STATIC_LIB_PROVIDER1_APK));
+ // Install the client
+ assertNull(install(STATIC_LIB_CONSUMER1_APK));
+
+ // Bind the service in consumer1 app to verify that the app should be killed when
+ // the static shared library is re-installed.
+ runDeviceTests(STATIC_LIB_TEST_APP_PKG, STATIC_LIB_TEST_APP_CLASS_NAME,
+ "testSamegradeStaticSharedLib_killDependentApp");
+ } finally {
+ getDevice().uninstallPackage(STATIC_LIB_CONSUMER1_PKG);
+ getDevice().uninstallPackage(STATIC_LIB_PROVIDER1_PKG);
+ getDevice().uninstallPackage(STATIC_LIB_PROVIDER_RECURSIVE_PKG);
+ }
+ }
+
private String install(String apk) throws DeviceNotAvailableException, FileNotFoundException {
return install(apk, false);
}
@@ -728,4 +856,32 @@
return getDevice().installPackage(mBuildHelper.getTestFile(apk), reinstall, false,
apk.contains("consumer") && mInstantMode ? "--instant" : "");
}
+
+ private boolean checkLibrary(String libName) throws DeviceNotAvailableException {
+ final CommandResult result = getDevice().executeShellV2Command("pm list libraries");
+ if (result.getStatus() != CommandStatus.SUCCESS) {
+ fail("Failed to execute shell command: pm list libraries");
+ }
+ return Arrays.stream(result.getStdout().split("\n"))
+ .map(line -> line.split(":")[1])
+ .collect(Collectors.toList()).contains(libName);
+ }
+
+ private void setGlobalSetting(String key, String value) throws DeviceNotAvailableException {
+ final boolean deleteKey = (value == null);
+ final StringBuilder cmd = new StringBuilder("settings ");
+ if (deleteKey) {
+ cmd.append("delete ");
+ } else {
+ cmd.append("put ");
+ }
+ cmd.append("global ").append(key);
+ if (!deleteKey) {
+ cmd.append(" ").append(value);
+ }
+ final CommandResult res = getDevice().executeShellV2Command(cmd.toString());
+ if (res.getStatus() != CommandStatus.SUCCESS) {
+ fail("Failed to execute shell command: " + cmd);
+ }
+ }
}
diff --git a/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml b/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml
index 3588a14..5994c7f 100755
--- a/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml
+++ b/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml
@@ -18,7 +18,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.os.inattentivesleeptests"
android:targetSandboxVersion="2">
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application>
+ <service android:name=".PartialWakeLockService"
+ android:exported="true" />
<activity android:name=".KeepScreenOnActivity"
android:exported="true">
<intent-filter>
diff --git a/hostsidetests/os/test-apps/InattentiveSleepTestApp/src/android/os/inattentivesleeptests/PartialWakeLockService.java b/hostsidetests/os/test-apps/InattentiveSleepTestApp/src/android/os/inattentivesleeptests/PartialWakeLockService.java
new file mode 100644
index 0000000..d64fa84
--- /dev/null
+++ b/hostsidetests/os/test-apps/InattentiveSleepTestApp/src/android/os/inattentivesleeptests/PartialWakeLockService.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.inattentivesleeptests;
+
+import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.PowerManager;
+
+/** Service that holds a partial wakelock until stopped */
+public class PartialWakeLockService extends Service {
+ private static final String TAG = "PartialWakeLockService";
+ private static final String NOTIFICATION_CHANNEL_ID = "Inattentive Sleep Tests";
+
+ private PowerManager.WakeLock mWakeLock;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+ NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
+ notificationManager.createNotificationChannel(channel);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (mWakeLock == null) {
+ PowerManager powerManager = getSystemService(PowerManager.class);
+ mWakeLock = powerManager.newWakeLock(PARTIAL_WAKE_LOCK, TAG);
+ mWakeLock.setReferenceCounted(false);
+ }
+ mWakeLock.acquire();
+
+ Notification notification = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+ .setContentTitle("Inattentive Sleep CTS Tests")
+ .setContentText("Keeping partial wakelock during the test")
+ .setSmallIcon(android.R.drawable.ic_secure)
+ .build();
+
+ startForeground(1, notification);
+
+ return Service.START_NOT_STICKY;
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mWakeLock != null) {
+ mWakeLock.release();
+ }
+ }
+}
diff --git a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/AndroidManifest.xml b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/AndroidManifest.xml
index 958db29..93d214f 100755
--- a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/AndroidManifest.xml
+++ b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/AndroidManifest.xml
@@ -31,6 +31,8 @@
android:version="1"
android:certDigest="E4:95:82:FF:3A:0A:A4:C5:58:9F:C5:FE:AA:C6:B7:D6:E7:57:19:9D:D0:C6:74:2D:F7:BF:37:C2:FF:EF:95:F5">
</uses-static-library>
+
+ <service android:name=".TestService" android:exported="true" />
</application>
<queries>
diff --git a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/src/android/os/lib/consumer1/TestService.java b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/src/android/os/lib/consumer1/TestService.java
new file mode 100644
index 0000000..b42c330
--- /dev/null
+++ b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/src/android/os/lib/consumer1/TestService.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.lib.consumer1;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+public class TestService extends Service {
+ private Binder mBinder = new Binder();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+}
diff --git a/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml b/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml
index 51589ea..0e5712c2a 100755
--- a/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml
+++ b/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml
@@ -21,6 +21,11 @@
<application>
</application>
+ <queries>
+ <package android:name="android.os.lib.provider"/>
+ <package android:name="android.os.lib.consumer1"/>
+ </queries>
+
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="android.os.lib.app"/>
</manifest>
diff --git a/hostsidetests/os/test-apps/StaticSharedLibTestApp/src/android/os/lib/app/StaticSharedLibsTests.java b/hostsidetests/os/test-apps/StaticSharedLibTestApp/src/android/os/lib/app/StaticSharedLibsTests.java
index 29bb524..cdf03d6 100644
--- a/hostsidetests/os/test-apps/StaticSharedLibTestApp/src/android/os/lib/app/StaticSharedLibsTests.java
+++ b/hostsidetests/os/test-apps/StaticSharedLibTestApp/src/android/os/lib/app/StaticSharedLibsTests.java
@@ -21,10 +21,16 @@
import static com.google.common.truth.Truth.assertThat;
import android.Manifest;
+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.SharedLibraryInfo;
+import android.os.IBinder;
import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.cts.install.lib.Install;
@@ -37,6 +43,8 @@
import org.junit.runner.RunWith;
import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* On-device tests driven by StaticSharedLibsHostTests.
@@ -44,12 +52,32 @@
@RunWith(AndroidJUnit4.class)
public class StaticSharedLibsTests {
+ private static final String APK_BASE_PATH = "/data/local/tmp/cts/hostside/os/";
+ private static final String STATIC_LIB_PROVIDER1_APK = APK_BASE_PATH
+ + "CtsStaticSharedLibProviderApp1.apk";
+ private static final String STATIC_LIB_PROVIDER1_NAME = "foo.bar.lib";
+ private static final Long STATIC_LIB_PROVIDER1_VERSION = 1L;
+
+ private static final String STATIC_LIB_PROVIDER2_APK = APK_BASE_PATH
+ + "CtsStaticSharedLibProviderApp2.apk";
+ private static final String STATIC_LIB_PROVIDER2_PKG = "android.os.lib.provider";
+ private static final String STATIC_LIB_PROVIDER2_NAME = "foo.bar.lib";
+ private static final Long STATIC_LIB_PROVIDER2_VERSION = 2L;
+
private static final String STATIC_LIB_PROVIDER5_PKG = "android.os.lib.provider";
private static final String STATIC_LIB_PROVIDER5_NAME = "android.os.lib.provider_2";
+ private static final Long STATIC_LIB_PROVIDER5_VERSION = 1L;
private static final TestApp TESTAPP_STATIC_LIB_PROVIDER5 = new TestApp(
"TestStaticSharedLibProvider5", STATIC_LIB_PROVIDER5_PKG, 1, /*isApex*/ false,
"CtsStaticSharedLibProviderApp5.apk");
+ public static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
+
+ private static final ComponentName CONSUMERAPP1_TEST_SERVICE = ComponentName.createRelative(
+ "android.os.lib.consumer1", ".TestService");
+
+ private final ServiceTestRule mServiceTestRule = new ServiceTestRule();
+
@Before
public void setUp() throws Exception {
InstrumentationRegistry
@@ -57,12 +85,10 @@
.getUiAutomation()
.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES);
- clear();
}
@After
public void tearDown() throws Exception {
- clear();
InstrumentationRegistry
.getInstrumentation()
.getUiAutomation()
@@ -71,31 +97,84 @@
@Test
public void testSamegradeStaticSharedLibFail() throws Exception {
- Install.single(TESTAPP_STATIC_LIB_PROVIDER5).commit();
- assertThat(getSharedLibraryInfo(STATIC_LIB_PROVIDER5_NAME)).isNotNull();
+ try {
+ Install.single(TESTAPP_STATIC_LIB_PROVIDER5).commit();
+ assertThat(
+ getSharedLibraryInfo(STATIC_LIB_PROVIDER5_NAME, STATIC_LIB_PROVIDER5_VERSION))
+ .isNotNull();
- InstallUtils.commitExpectingFailure(AssertionError.class,
- "Packages declaring static-shared libs cannot be updated",
- Install.single(TESTAPP_STATIC_LIB_PROVIDER5));
+ InstallUtils.commitExpectingFailure(AssertionError.class,
+ "Packages declaring static-shared libs cannot be updated",
+ Install.single(TESTAPP_STATIC_LIB_PROVIDER5));
+ } finally {
+ uninstallPackage(STATIC_LIB_PROVIDER5_PKG);
+ }
}
- private void clear() {
- uninstallSharedLibrary(STATIC_LIB_PROVIDER5_PKG, STATIC_LIB_PROVIDER5_NAME);
+ @Test
+ public void testInstallStaticSharedLib_notKillDependentApp() throws Exception {
+ final Intent intent = new Intent();
+ intent.setComponent(CONSUMERAPP1_TEST_SERVICE);
+ final CountDownLatch kill = new CountDownLatch(1);
+ mServiceTestRule.bindService(intent, new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ }
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ kill.countDown();
+ }
+ }, Context.BIND_AUTO_CREATE);
+
+ try {
+ installPackage(STATIC_LIB_PROVIDER2_APK);
+ assertThat(
+ getSharedLibraryInfo(STATIC_LIB_PROVIDER2_NAME, STATIC_LIB_PROVIDER2_VERSION))
+ .isNotNull();
+
+ assertThat(kill.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isFalse();
+ } finally {
+ uninstallPackage(STATIC_LIB_PROVIDER2_PKG);
+ }
}
- private SharedLibraryInfo getSharedLibraryInfo(String libName) {
+ @Test
+ public void testSamegradeStaticSharedLib_killDependentApp() throws Exception {
+ final Intent intent = new Intent();
+ intent.setComponent(CONSUMERAPP1_TEST_SERVICE);
+ final CountDownLatch kill = new CountDownLatch(1);
+ mServiceTestRule.bindService(intent, new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ }
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ kill.countDown();
+ }
+ }, Context.BIND_AUTO_CREATE);
+ assertThat(
+ getSharedLibraryInfo(STATIC_LIB_PROVIDER1_NAME, STATIC_LIB_PROVIDER1_VERSION))
+ .isNotNull();
+
+ installPackage(STATIC_LIB_PROVIDER1_APK);
+ assertThat(kill.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+ }
+
+ private SharedLibraryInfo getSharedLibraryInfo(String libName, long version) {
final PackageManager packageManager = InstrumentationRegistry.getContext()
.getPackageManager();
- final Optional<SharedLibraryInfo> libraryInfo = packageManager.getSharedLibraries(0)
- .stream().filter(lib -> lib.getName().equals(libName)).findAny();
+ final Optional<SharedLibraryInfo> libraryInfo =
+ packageManager.getSharedLibraries(0 /* flags */).stream().filter(
+ lib -> lib.getName().equals(libName) && lib.getLongVersion() == version)
+ .findFirst();
return libraryInfo.isPresent() ? libraryInfo.get() : null;
}
- private void uninstallSharedLibrary(String packageName, String libName) {
- if (getSharedLibraryInfo(libName) == null) {
- return;
- }
+ private boolean installPackage(String apkPath) {
+ return runShellCommand("pm install -t " + apkPath).equals("Success\n");
+ }
+
+ private void uninstallPackage(String packageName) {
runShellCommand("pm uninstall " + packageName);
- assertThat(getSharedLibraryInfo(libName)).isNull();
}
}
diff --git a/hostsidetests/os/test-apps/TEST_MAPPING b/hostsidetests/os/test-apps/TEST_MAPPING
new file mode 100644
index 0000000..8ee69ff
--- /dev/null
+++ b/hostsidetests/os/test-apps/TEST_MAPPING
@@ -0,0 +1,22 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsOsHostTestCases",
+ "file_patterns": [
+ "(/|^)StaticSharedLib.*",
+ "(/|^)StaticSharedNativeLib.*"
+ ],
+ "options": [
+ {
+ "include-filter": "android.os.cts.StaticSharedLibsHostTests"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
diff --git a/hostsidetests/os/test_mappings/packagemanager/TEST_MAPPING b/hostsidetests/os/test_mappings/packagemanager/TEST_MAPPING
new file mode 100644
index 0000000..72ed050
--- /dev/null
+++ b/hostsidetests/os/test_mappings/packagemanager/TEST_MAPPING
@@ -0,0 +1,21 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsOsHostTestCases",
+ "options": [
+ {
+ "include-filter": "android.os.cts.StaticSharedLibsHostTests"
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
diff --git a/hostsidetests/packagemanager/TEST_MAPPING b/hostsidetests/packagemanager/TEST_MAPPING
new file mode 100644
index 0000000..ab7465f
--- /dev/null
+++ b/hostsidetests/packagemanager/TEST_MAPPING
@@ -0,0 +1,33 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsCodePathHostTestCases"
+ },
+ {
+ "name": "CtsDomainVerificationHostTestCases"
+ },
+ {
+ "name": "CtsExtractNativeLibsHostTestCases"
+ },
+ {
+ "name": "CtsInstalledLoadingProgressHostTests"
+ },
+ {
+ "name": "CtsPackageManagerMultiUserHostTestCases"
+ },
+ {
+ "name": "CtsPackageManagerParsingHostTestCases"
+ },
+ {
+ "name": "CtsPackageManagerPreferredActivityHostTestCases"
+ },
+ {
+ "name": "CtsPackageManagerStatsHostTestCases"
+ }
+ ],
+ "presubmit-large": [
+ {
+ "name": "CtsDynamicMimeHostTestCases"
+ }
+ ]
+}
diff --git a/hostsidetests/packagemanager/codepath/Android.bp b/hostsidetests/packagemanager/codepath/Android.bp
deleted file mode 100644
index 1927ee3..0000000
--- a/hostsidetests/packagemanager/codepath/Android.bp
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_test_host {
- name: "CtsCodePathHostTestCases",
- defaults: ["cts_defaults"],
- srcs: ["src/**/*.java"],
- // tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
- ],
- libs: [
- "cts-tradefed",
- "tradefed",
- "compatibility-host-util",
- ],
-}
diff --git a/hostsidetests/packagemanager/codepath/AndroidTest.xml b/hostsidetests/packagemanager/codepath/AndroidTest.xml
deleted file mode 100644
index 083a00f..0000000
--- a/hostsidetests/packagemanager/codepath/AndroidTest.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT 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 package installation code path host test cases">
- <option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="framework" />
- <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
- <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
- <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
- <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
- <option name="jar" value="CtsCodePathHostTestCases.jar" />
- </test>
-</configuration>
-
diff --git a/hostsidetests/packagemanager/codepath/app/Android.bp b/hostsidetests/packagemanager/codepath/app/Android.bp
deleted file mode 100644
index 666244d..0000000
--- a/hostsidetests/packagemanager/codepath/app/Android.bp
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
- name: "CodePathTestApp",
- defaults: ["cts_defaults"],
- sdk_version: "current",
- srcs: ["src/**/*.java",],
- test_suites: [
- "cts",
- "general-tests",
- ],
- static_libs: ["androidx.test.rules"],
- compile_multilib: "both",
-}
diff --git a/hostsidetests/packagemanager/codepath/app/AndroidManifest.xml b/hostsidetests/packagemanager/codepath/app/AndroidManifest.xml
deleted file mode 100644
index a9d5a03..0000000
--- a/hostsidetests/packagemanager/codepath/app/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT 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.tests.codepath.app" >
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation
- android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.tests.codepath.app" />
-</manifest>
diff --git a/hostsidetests/packagemanager/codepath/app/src/com/android/tests/codepath/app/CodePathDeviceTest.java b/hostsidetests/packagemanager/codepath/app/src/com/android/tests/codepath/app/CodePathDeviceTest.java
deleted file mode 100644
index dd909c8..0000000
--- a/hostsidetests/packagemanager/codepath/app/src/com/android/tests/codepath/app/CodePathDeviceTest.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.tests.codepath.app;
-
-import android.content.pm.PackageInfo;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class CodePathDeviceTest {
- @Test
- public void testCodePathMatchesExpected() throws Exception {
- String expectedCodePath =
- InstrumentationRegistry.getArguments().getString("expectedCodePath");
- String packageName =
- InstrumentationRegistry.getInstrumentation().getContext().getPackageName();
- PackageInfo pi = InstrumentationRegistry.getInstrumentation().getContext()
- .getPackageManager().getPackageInfo(packageName, 0);
- String apkPath = pi.applicationInfo.sourceDir;
- Assert.assertTrue(apkPath.startsWith(expectedCodePath));
- }
-}
diff --git a/hostsidetests/packagemanager/codepath/src/com/android/tests/codepath/host/CodePathTest.java b/hostsidetests/packagemanager/codepath/src/com/android/tests/codepath/host/CodePathTest.java
deleted file mode 100644
index 32763dd..0000000
--- a/hostsidetests/packagemanager/codepath/src/com/android/tests/codepath/host/CodePathTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.tests.codepath.host;
-
-import android.platform.test.annotations.AppModeFull;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.PackageInfo;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.HashMap;
-import java.util.Map;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class CodePathTest extends BaseHostJUnit4Test {
- private static final String TEST_APK = "CodePathTestApp.apk";
- private static final String TEST_PACKAGE = "com.android.tests.codepath.app";
- private static final String TEST_CLASS = TEST_PACKAGE + "." + "CodePathDeviceTest";
- private static final String TEST_METHOD = "testCodePathMatchesExpected";
- private static final String CODE_PATH_ROOT = "/data/app";
- private static final long DEFAULT_TIMEOUT = 10 * 60 * 1000L;
-
- /** Uninstall apps after tests. */
- @After
- public void cleanUp() throws Exception {
- uninstallPackage(getDevice(), TEST_PACKAGE);
- Assert.assertFalse(isPackageInstalled(TEST_PACKAGE));
- }
-
- @Test
- @AppModeFull
- public void testAppInstallsWithReboot() throws Exception {
- installPackage(TEST_APK);
- Assert.assertTrue(isPackageInstalled(TEST_PACKAGE));
- final String codePathFromDumpsys = getCodePathFromDumpsys(TEST_PACKAGE);
- Assert.assertTrue(codePathFromDumpsys.startsWith(CODE_PATH_ROOT));
- runDeviceTest(codePathFromDumpsys);
- // Code paths should still be valid after reboot
- getDevice().reboot();
- final String codePathFromDumpsysAfterReboot = getCodePathFromDumpsys(TEST_PACKAGE);
- Assert.assertEquals(codePathFromDumpsys, codePathFromDumpsysAfterReboot);
- runDeviceTest(codePathFromDumpsys);
- }
-
- private String getCodePathFromDumpsys(String packageName)
- throws DeviceNotAvailableException {
- PackageInfo packageInfo = getDevice().getAppPackageInfo(packageName);
- return packageInfo.getCodePath();
- }
-
- private void runDeviceTest(String codePathToMatch) throws Exception {
- final Map<String, String> testArgs = new HashMap<>();
- testArgs.put("expectedCodePath", codePathToMatch);
- runDeviceTests(getDevice(), null, TEST_PACKAGE, TEST_CLASS, TEST_METHOD,
- null, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT,
- 0L, true, false, testArgs);
- }
-}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileAllowParentLinkingTests.kt b/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileAllowParentLinkingTests.kt
index bf226ff..152a2b5 100644
--- a/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileAllowParentLinkingTests.kt
+++ b/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileAllowParentLinkingTests.kt
@@ -18,6 +18,7 @@
import com.android.bedstead.harrier.BedsteadJUnit4
import com.android.bedstead.harrier.DeviceState
+import com.android.bedstead.harrier.UserType
import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile
import com.android.bedstead.harrier.annotations.Postsubmit
import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser
@@ -27,7 +28,7 @@
import org.junit.Test
import org.junit.runner.RunWith
-@EnsureHasWorkProfile(forUser = DeviceState.UserType.PRIMARY_USER)
+@EnsureHasWorkProfile(forUser = UserType.PRIMARY_USER)
@RunWith(BedsteadJUnit4::class)
class DomainVerificationWorkProfileAllowParentLinkingTests :
DomainVerificationWorkProfileTestsBase() {
diff --git a/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileCrossProfileIntentTests.kt b/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileCrossProfileIntentTests.kt
index 4062d98..4586631 100644
--- a/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileCrossProfileIntentTests.kt
+++ b/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileCrossProfileIntentTests.kt
@@ -21,6 +21,7 @@
import android.content.IntentFilter
import com.android.bedstead.harrier.BedsteadJUnit4
import com.android.bedstead.harrier.DeviceState
+import com.android.bedstead.harrier.UserType
import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile
import com.android.bedstead.harrier.annotations.Postsubmit
import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser
@@ -31,7 +32,7 @@
import org.junit.Test
import org.junit.runner.RunWith
-@EnsureHasWorkProfile(forUser = DeviceState.UserType.PRIMARY_USER)
+@EnsureHasWorkProfile(forUser = UserType.PRIMARY_USER)
@RunWith(BedsteadJUnit4::class)
class DomainVerificationWorkProfileCrossProfileIntentTests :
DomainVerificationWorkProfileTestsBase() {
diff --git a/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileTestsBase.kt b/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileTestsBase.kt
index dff94b8..21a300a 100644
--- a/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileTestsBase.kt
+++ b/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileTestsBase.kt
@@ -22,6 +22,7 @@
import android.net.Uri
import com.android.bedstead.harrier.BedsteadJUnit4
import com.android.bedstead.harrier.DeviceState
+import com.android.bedstead.harrier.UserType
import com.android.bedstead.harrier.annotations.AfterClass
import com.android.bedstead.harrier.annotations.BeforeClass
import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile
@@ -53,7 +54,7 @@
import org.junit.Test
import org.junit.runner.RunWith
-@EnsureHasWorkProfile(forUser = DeviceState.UserType.PRIMARY_USER)
+@EnsureHasWorkProfile(forUser = UserType.PRIMARY_USER)
@RunWith(BedsteadJUnit4::class)
abstract class DomainVerificationWorkProfileTestsBase {
@@ -101,7 +102,7 @@
@BeforeClass
fun installApks() {
personalUser = deviceState.primaryUser()
- workUser = deviceState.workProfile(DeviceState.UserType.PRIMARY_USER)
+ workUser = deviceState.workProfile(UserType.PRIMARY_USER)
personalBrowsers = collectBrowsers(personalUser)
workBrowsers = collectBrowsers(workUser)
TestApis.packages().run {
diff --git a/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkUtils.kt b/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkUtils.kt
index 955a078..aba31b9 100644
--- a/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkUtils.kt
+++ b/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkUtils.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.os.UserManager
import com.android.bedstead.harrier.DeviceState
+import com.android.bedstead.harrier.UserType
import com.android.bedstead.nene.TestApis
import com.android.bedstead.nene.users.UserReference
import android.app.admin.RemoteDevicePolicyManager
@@ -37,7 +38,7 @@
}
internal fun DeviceState.getWorkDevicePolicyManager() =
- profileOwner(workProfile(DeviceState.UserType.PRIMARY_USER))!!
+ profileOwner(workProfile(UserType.PRIMARY_USER))!!
.devicePolicyManager()
internal fun <T> withUserContext(user: UserReference, block: (context: Context) -> T) =
diff --git a/hostsidetests/packagemanager/installedloadingprogess/hostside/src/com/android/tests/loadingprogress/host/IncrementalLoadingProgressTest.java b/hostsidetests/packagemanager/installedloadingprogess/hostside/src/com/android/tests/loadingprogress/host/IncrementalLoadingProgressTest.java
index db9a827..c3423bc 100644
--- a/hostsidetests/packagemanager/installedloadingprogess/hostside/src/com/android/tests/loadingprogress/host/IncrementalLoadingProgressTest.java
+++ b/hostsidetests/packagemanager/installedloadingprogess/hostside/src/com/android/tests/loadingprogress/host/IncrementalLoadingProgressTest.java
@@ -39,6 +39,8 @@
import java.io.File;
import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
@@ -125,6 +127,26 @@
@LargeTest
@Test
+ public void testGetLoadingProgressDuringMigration() throws Exception {
+ // Check partial loading progress
+ assertTrue(runDeviceTests(DEVICE_TEST_PACKAGE_NAME, TEST_CLASS_NAME,
+ "testGetPartialLoadingProgress"));
+ final List<File> apks = new ArrayList<>(2);
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+ final File base_apk = buildHelper.getTestFile(TEST_APK);
+ assertNotNull(base_apk);
+ apks.add(base_apk);
+ final File split_apk = buildHelper.getTestFile(TEST_SPLIT_APK);
+ assertNotNull(split_apk);
+ apks.add(split_apk);
+ // Trigger app migration through normal package installation.
+ getDevice().installPackages(apks, false, "-t");
+ assertTrue(runDeviceTests(DEVICE_TEST_PACKAGE_NAME, TEST_CLASS_NAME,
+ "testGetFullLoadingProgress"));
+ }
+
+ @LargeTest
+ @Test
public void testOnPackageLoadingProgressChangedCalledWithPartialLoaded() throws Exception {
assertTrue(runDeviceTests(DEVICE_TEST_PACKAGE_NAME, TEST_CLASS_NAME,
"testOnPackageLoadingProgressChangedCalledWithPartialLoaded"));
diff --git a/hostsidetests/packagemanager/packagesetting/Android.bp b/hostsidetests/packagemanager/packagesetting/Android.bp
new file mode 100644
index 0000000..ba34621
--- /dev/null
+++ b/hostsidetests/packagemanager/packagesetting/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+ name: "CtsPackageSettingHostTestCases",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.java"],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "compatibility-host-util",
+ ],
+}
diff --git a/hostsidetests/packagemanager/packagesetting/AndroidTest.xml b/hostsidetests/packagemanager/packagesetting/AndroidTest.xml
new file mode 100644
index 0000000..9218853
--- /dev/null
+++ b/hostsidetests/packagemanager/packagesetting/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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 package installation code path host test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsPackageSettingHostTestCases.jar" />
+ </test>
+</configuration>
+
diff --git a/hostsidetests/packagemanager/packagesetting/app/Android.bp b/hostsidetests/packagemanager/packagesetting/app/Android.bp
new file mode 100644
index 0000000..2707570
--- /dev/null
+++ b/hostsidetests/packagemanager/packagesetting/app/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "PackageSettingTestApp",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.java"],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ static_libs: ["androidx.test.rules"],
+ compile_multilib: "both",
+ platform_apis: true,
+}
diff --git a/hostsidetests/packagemanager/packagesetting/app/AndroidManifest.xml b/hostsidetests/packagemanager/packagesetting/app/AndroidManifest.xml
new file mode 100644
index 0000000..0072f74
--- /dev/null
+++ b/hostsidetests/packagemanager/packagesetting/app/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.tests.packagesetting.app" >
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.tests.packagesetting.app" />
+</manifest>
diff --git a/hostsidetests/packagemanager/packagesetting/app/src/com/android/tests/packagesetting/app/PackageSettingDeviceTest.java b/hostsidetests/packagemanager/packagesetting/app/src/com/android/tests/packagesetting/app/PackageSettingDeviceTest.java
new file mode 100644
index 0000000..ca3c4ed
--- /dev/null
+++ b/hostsidetests/packagemanager/packagesetting/app/src/com/android/tests/packagesetting/app/PackageSettingDeviceTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.tests.packagesetting.app;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PackageSettingDeviceTest {
+ @Test
+ public void testCodePathMatchesExpected() throws Exception {
+ String expectedCodePath =
+ InstrumentationRegistry.getArguments().getString("expectedCodePath");
+ String packageName =
+ InstrumentationRegistry.getInstrumentation().getContext().getPackageName();
+ PackageInfo pi = InstrumentationRegistry.getInstrumentation().getContext()
+ .getPackageManager().getPackageInfo(packageName, 0);
+ String apkPath = pi.applicationInfo.sourceDir;
+ Assert.assertTrue(apkPath.startsWith(expectedCodePath));
+ }
+
+ @Test
+ public void testFirstInstallTimeMatchesExpected() throws Exception {
+ final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ String packageName =
+ InstrumentationRegistry.getInstrumentation().getContext().getPackageName();
+ String userIdStr = InstrumentationRegistry.getArguments().getString("userId");
+ String expectedFirstInstallTimeStr = InstrumentationRegistry.getArguments().getString(
+ "expectedFirstInstallTime");
+ final int userId = Integer.parseInt(userIdStr);
+ final long expectedFirstInstallTime = Long.parseLong(expectedFirstInstallTimeStr);
+ final Context contextAsUser = context.createContextAsUser(UserHandle.of(userId), 0);
+ PackageInfo pi = contextAsUser.getPackageManager().getPackageInfo(packageName,
+ PackageManager.PackageInfoFlags.of(0));
+ Assert.assertTrue(Math.abs(expectedFirstInstallTime - pi.firstInstallTime) < 1000 /* ms */);
+ }
+}
diff --git a/hostsidetests/packagemanager/packagesetting/src/com/android/tests/packagesetting/host/PackageSettingTest.java b/hostsidetests/packagemanager/packagesetting/src/com/android/tests/packagesetting/host/PackageSettingTest.java
new file mode 100644
index 0000000..0b66e17
--- /dev/null
+++ b/hostsidetests/packagemanager/packagesetting/src/com/android/tests/packagesetting/host/PackageSettingTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.tests.packagesetting.host;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.PackageInfo;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TimeZone;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PackageSettingTest extends BaseHostJUnit4Test {
+ private static final String TEST_APK = "PackageSettingTestApp.apk";
+ private static final String TEST_PACKAGE = "com.android.tests.packagesetting.app";
+ private static final String TEST_CLASS = TEST_PACKAGE + "." + "PackageSettingDeviceTest";
+ private static final String CODE_PATH_ROOT = "/data/app";
+ private static final long DEFAULT_TIMEOUT = 10 * 60 * 1000L;
+ private int mSecondUser = -1;
+
+ /** Uninstall apps after tests. */
+ @After
+ public void cleanUp() throws Exception {
+ uninstallPackage(getDevice(), TEST_PACKAGE);
+ Assert.assertFalse(isPackageInstalled(TEST_PACKAGE));
+ if (mSecondUser != -1) {
+ stopAndRemoveUser(mSecondUser);
+ }
+ }
+
+ @Test
+ @AppModeFull
+ public void testAppInstallsWithReboot() throws Exception {
+ installPackage(TEST_APK);
+ Assert.assertTrue(isPackageInstalled(TEST_PACKAGE));
+ final String codePathFromDumpsys = getCodePathFromDumpsys(TEST_PACKAGE);
+ Assert.assertTrue(codePathFromDumpsys.startsWith(CODE_PATH_ROOT));
+ testCodePathMatchesDumpsys(codePathFromDumpsys);
+ // Code paths should still be valid after reboot
+ getDevice().reboot();
+ final String codePathFromDumpsysAfterReboot = getCodePathFromDumpsys(TEST_PACKAGE);
+ Assert.assertEquals(codePathFromDumpsys, codePathFromDumpsysAfterReboot);
+ testCodePathMatchesDumpsys(codePathFromDumpsys);
+ }
+
+ private String getCodePathFromDumpsys(String packageName)
+ throws DeviceNotAvailableException {
+ PackageInfo packageInfo = getDevice().getAppPackageInfo(packageName);
+ return packageInfo.getCodePath();
+ }
+
+ private void testCodePathMatchesDumpsys(String codePathToMatch) throws Exception {
+ final Map<String, String> testArgs = new HashMap<>();
+ testArgs.put("expectedCodePath", codePathToMatch);
+ runDeviceTests(getDevice(), null, TEST_PACKAGE, TEST_CLASS, "testCodePathMatchesExpected",
+ null, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, 0L, true, false, testArgs);
+ }
+
+ @Test
+ @AppModeFull
+ public void testFirstInstallTimeWithReboot() throws Exception {
+ installPackage(TEST_APK);
+ final int currentUser = getDevice().getCurrentUser();
+ final String firstInstallTimeForCurrentUser = getFirstInstallTimeForUserFromDumpsys(
+ TEST_PACKAGE, currentUser);
+ Assert.assertNotNull(firstInstallTimeForCurrentUser);
+ testFirstInstallTimeMatchesDumpsys(firstInstallTimeForCurrentUser, currentUser);
+ // firstInstallTime should be the same after reboot
+ getDevice().reboot();
+ Assert.assertEquals(firstInstallTimeForCurrentUser,
+ getFirstInstallTimeForUserFromDumpsys(TEST_PACKAGE, currentUser));
+
+ mSecondUser = createAndStartSecondUser();
+ installPackageOnExistingUser(TEST_PACKAGE, mSecondUser);
+ final String firstInstallTimeForSecondUser = getFirstInstallTimeForUserFromDumpsys(
+ TEST_PACKAGE, mSecondUser);
+ Assert.assertNotNull(firstInstallTimeForSecondUser);
+ Assert.assertNotEquals(firstInstallTimeForCurrentUser, firstInstallTimeForSecondUser);
+ testFirstInstallTimeMatchesDumpsys(firstInstallTimeForSecondUser, mSecondUser);
+ getDevice().reboot();
+ Assert.assertEquals(firstInstallTimeForSecondUser,
+ getFirstInstallTimeForUserFromDumpsys(TEST_PACKAGE, mSecondUser));
+ }
+
+ private int createAndStartSecondUser() throws Exception {
+ String output = getDevice().executeShellCommand("pm create-user SecondUser");
+ Assert.assertTrue(output.startsWith("Success"));
+ int userId = Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
+ output = getDevice().executeShellCommand("am start-user -w " + userId);
+ Assert.assertFalse(output.startsWith("Error"));
+ output = getDevice().executeShellCommand("am get-started-user-state " + userId);
+ Assert.assertTrue(output.contains("RUNNING_UNLOCKED"));
+ return userId;
+ }
+
+ private void stopAndRemoveUser(int userId) throws Exception {
+ getDevice().executeShellCommand("am stop-user -w -f " + userId);
+ getDevice().executeShellCommand("pm remove-user " + userId);
+ }
+
+ private void installPackageOnExistingUser(String packageName, int userId) throws Exception {
+ final String output = getDevice().executeShellCommand(
+ String.format("pm install-existing --user %d %s", userId, packageName));
+ Assert.assertEquals("Package " + packageName + " installed for user: " + userId + "\n",
+ output);
+ }
+
+ private String getFirstInstallTimeForUserFromDumpsys(String packageName, int userId)
+ throws Exception {
+ PackageInfo packageInfo = getDevice().getAppPackageInfo(packageName);
+ return packageInfo.getFirstInstallTime(userId);
+ }
+
+ private void testFirstInstallTimeMatchesDumpsys(String firstInstallTime, int userId)
+ throws Exception {
+ final Map<String, String> testArgs = new HashMap<>();
+ // Notice the printed timestamp in dumpsys is formatted and has lost sub-second precision
+ final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ sdf.setTimeZone(TimeZone.getTimeZone(getDeviceTimezone()));
+ final long firstInstallTs = sdf.parse(firstInstallTime).getTime();
+ testArgs.put("userId", String.valueOf(userId));
+ testArgs.put("expectedFirstInstallTime", String.valueOf(firstInstallTs));
+ runDeviceTests(getDevice(), null, TEST_PACKAGE, TEST_CLASS,
+ "testFirstInstallTimeMatchesExpected", null, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT,
+ 0L, true, false, testArgs);
+ }
+
+ private String getDeviceTimezone() throws Exception {
+ final String timezone = getDevice().getProperty("persist.sys.timezone");
+ if (timezone != null) {
+ return timezone.trim();
+ }
+ return "GMT";
+ }
+
+}
diff --git a/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java b/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
index a34da7b..36d4bb0 100644
--- a/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
+++ b/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
@@ -671,6 +671,11 @@
assertThat(committed).causePackagesContainsExactly(TestApp.A2);
assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+ // This staged session should fail when there is already a staged rollback
+ InstallUtils.commitExpectingFailure(AssertionError.class,
+ "Session was failed by rollback",
+ Install.single(TestApp.C1).setStaged());
+
// Assert that blocking staged session is failed
final PackageInstaller.SessionInfo sessionA = InstallUtils.getStagedSessionInfo(sessionIdA);
assertThat(sessionA).isNotNull();
diff --git a/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java b/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
index f373d47..88e926a 100644
--- a/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
+++ b/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
@@ -81,6 +81,7 @@
+ "--only-parent); do pm install-abandon $i; done");
getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.A");
getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.B");
+ getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.C");
run("cleanUp");
mHostUtils.uninstallShimApexIfNecessary();
}
diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp
index 61526d2..247992a 100644
--- a/hostsidetests/scopedstorage/Android.bp
+++ b/hostsidetests/scopedstorage/Android.bp
@@ -21,34 +21,49 @@
manifest: "ScopedStorageTestHelper/TestAppA.xml",
static_libs: ["cts-scopedstorage-lib"],
sdk_version: "test_current",
- target_sdk_version: "31",
+ target_sdk_version: "current",
min_sdk_version: "30",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
}
+
android_test_helper_app {
name: "CtsScopedStorageTestAppB",
manifest: "ScopedStorageTestHelper/TestAppB.xml",
static_libs: ["cts-scopedstorage-lib"],
sdk_version: "test_current",
- target_sdk_version: "31",
+ target_sdk_version: "current",
min_sdk_version: "30",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
}
+
android_test_helper_app {
name: "CtsScopedStorageTestAppC",
manifest: "ScopedStorageTestHelper/TestAppC.xml",
static_libs: ["cts-scopedstorage-lib"],
sdk_version: "test_current",
- target_sdk_version: "31",
+ target_sdk_version: "current",
min_sdk_version: "30",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
}
+
android_test_helper_app {
name: "CtsScopedStorageTestAppC30",
manifest: "ScopedStorageTestHelper/TestAppC30.xml",
@@ -58,8 +73,13 @@
min_sdk_version: "30",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts",
+ "cts",
+ ],
}
+
android_test_helper_app {
name: "CtsScopedStorageTestAppCLegacy",
manifest: "ScopedStorageTestHelper/TestAppCLegacy.xml",
@@ -69,8 +89,13 @@
min_sdk_version: "28",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
}
+
android_test_helper_app {
name: "CtsScopedStorageTestAppDLegacy",
manifest: "ScopedStorageTestHelper/TestAppDLegacy.xml",
@@ -80,7 +105,11 @@
min_sdk_version: "28",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
}
android_test_helper_app {
@@ -88,34 +117,49 @@
manifest: "ScopedStorageTestHelper/TestAppFileManager.xml",
static_libs: ["cts-scopedstorage-lib"],
sdk_version: "test_current",
- target_sdk_version: "31",
+ target_sdk_version: "current",
min_sdk_version: "30",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
}
+
android_test_helper_app {
name: "CtsScopedStorageTestAppFileManagerBypassDB",
manifest: "ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml",
static_libs: ["cts-scopedstorage-lib"],
sdk_version: "test_current",
- target_sdk_version: "31",
+ target_sdk_version: "current",
min_sdk_version: "30",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts",
+ "cts",
+ ],
}
+
android_test_helper_app {
name: "CtsScopedStorageTestAppSystemGalleryBypassDB",
manifest: "ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml",
static_libs: ["cts-scopedstorage-lib"],
sdk_version: "test_current",
- target_sdk_version: "31",
+ target_sdk_version: "current",
min_sdk_version: "30",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts",
+ "cts",
+ ],
}
+
android_test_helper_app {
name: "CtsScopedStorageTestAppSystemGallery30BypassDB",
manifest: "ScopedStorageTestHelper/TestAppSystemGallery30BypassDB.xml",
@@ -125,7 +169,11 @@
min_sdk_version: "30",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts",
+ "cts",
+ ],
}
android_test_helper_app {
@@ -150,52 +198,85 @@
name: "ScopedStorageTest",
manifest: "AndroidManifest.xml",
srcs: ["src/**/*.java"],
- static_libs: ["truth-prebuilt", "cts-scopedstorage-lib"],
+ static_libs: [
+ "truth-prebuilt",
+ "cts-scopedstorage-lib",
+ ],
compile_multilib: "both",
- test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
sdk_version: "test_current",
- target_sdk_version: "31",
+ target_sdk_version: "current",
min_sdk_version: "30",
java_resources: [
":CtsScopedStorageTestAppA",
":CtsScopedStorageTestAppB",
":CtsScopedStorageTestAppC",
":CtsScopedStorageTestAppCLegacy",
- ]
+ ],
}
android_test {
name: "LegacyStorageTest",
manifest: "legacy/AndroidManifest.xml",
srcs: ["legacy/src/**/*.java"],
- static_libs: ["truth-prebuilt", "cts-scopedstorage-lib"],
+ static_libs: [
+ "truth-prebuilt",
+ "cts-scopedstorage-lib",
+ ],
compile_multilib: "both",
- test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
sdk_version: "test_current",
target_sdk_version: "29",
min_sdk_version: "30",
java_resources: [
":CtsScopedStorageTestAppA",
- ]
+ ],
}
java_test_host {
name: "CtsScopedStorageCoreHostTest",
- srcs: [
+ srcs: [
"host/src/android/scopedstorage/cts/host/ScopedStorageCoreHostTest.java",
- "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java"
+ "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java",
],
- libs: ["cts-tradefed", "tradefed", "testng"],
- test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "testng",
+ ],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
test_config: "CoreTest.xml",
}
java_test_host {
name: "CtsScopedStorageHostTest",
srcs: ["host/src/**/*.java"],
- libs: ["cts-tradefed", "tradefed", "testng"],
- static_libs: ["modules-utils-build-testing", "compatibility-host-util"],
- test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "testng",
+ ],
+ static_libs: [
+ "modules-utils-build-testing",
+ "compatibility-host-util",
+ ],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
test_config: "AndroidTest.xml",
data: [
":CtsLegacyStorageTestAppRequestLegacy",
@@ -205,13 +286,23 @@
java_test_host {
name: "GtsPreserveLegacyStorageHostTest",
- srcs: [
+ srcs: [
"host/src/android/scopedstorage/cts/host/PreserveLegacyStorageHostTest.java",
- "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java"
+ "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java",
],
- libs: ["cts-tradefed", "tradefed", "testng"],
- static_libs: ["modules-utils-build-testing", "compatibility-host-util"],
- test_suites: ["general-tests", "gts"],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "testng",
+ ],
+ static_libs: [
+ "modules-utils-build-testing",
+ "compatibility-host-util",
+ ],
+ test_suites: [
+ "general-tests",
+ "gts",
+ ],
test_config: "AndroidPreserveLegacyTest.xml",
data: [
":CtsLegacyStorageTestAppRequestLegacy",
@@ -222,21 +313,20 @@
java_test_host {
name: "CtsScopedStoragePublicVolumeHostTest",
srcs: ["host/src/**/*.java"],
- libs: ["cts-tradefed", "tradefed", "testng"],
- static_libs: ["modules-utils-build-testing", "compatibility-host-util"],
- test_suites: ["general-tests", "mts-mediaprovider"],
- test_config: "PublicVolumeTest.xml",
-}
-
-java_test_host {
- name: "CtsAppCloningHostTest",
- srcs: [
- "host/src/android/scopedstorage/cts/host/AppCloningHostTest.java",
- "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java"
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "testng",
],
- libs: ["cts-tradefed", "tradefed", "testng"],
- test_suites: ["general-tests", "mts-mediaprovider", "cts"],
- test_config: "AndroidTestAppCloning.xml",
+ static_libs: [
+ "modules-utils-build-testing",
+ "compatibility-host-util",
+ ],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ ],
+ test_config: "PublicVolumeTest.xml",
}
android_test {
@@ -244,13 +334,24 @@
manifest: "device/AndroidManifest.xml",
test_config: "device/AndroidTest.xml",
srcs: ["device/**/*.java"],
- static_libs: ["truth-prebuilt", "cts-scopedstorage-lib",],
+ static_libs: [
+ "truth-prebuilt",
+ "cts-scopedstorage-lib",
+ ],
compile_multilib: "both",
- test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
sdk_version: "test_current",
- target_sdk_version: "31",
+ target_sdk_version: "current",
min_sdk_version: "30",
- libs: ["android.test.base", "android.test.mock", "android.test.runner",],
+ libs: [
+ "android.test.base",
+ "android.test.mock",
+ "android.test.runner",
+ ],
java_resources: [
":CtsScopedStorageTestAppA",
":CtsScopedStorageTestAppB",
@@ -262,5 +363,5 @@
":CtsScopedStorageTestAppFileManagerBypassDB",
":CtsScopedStorageTestAppSystemGalleryBypassDB",
":CtsScopedStorageTestAppSystemGallery30BypassDB",
- ]
+ ],
}
diff --git a/hostsidetests/scopedstorage/AndroidTest.xml b/hostsidetests/scopedstorage/AndroidTest.xml
index 42a3a36..320d541 100644
--- a/hostsidetests/scopedstorage/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/AndroidTest.xml
@@ -28,6 +28,12 @@
<option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
<option name="test-file-name" value="CtsLegacyStorageTestAppRequestLegacy.apk" />
</target_preparer>
+
+ <option
+ name="config-descriptor:metadata"
+ key="mainline-param"
+ value="com.google.android.mediaprovider.apex" />
+
<test class="com.android.tradefed.testtype.HostTest" >
<option name="class" value="android.scopedstorage.cts.host.LegacyStorageHostTest" />
<option name="class" value="android.scopedstorage.cts.host.PreserveLegacyStorageHostTest" />
diff --git a/hostsidetests/scopedstorage/AndroidTestAppCloning.xml b/hostsidetests/scopedstorage/AndroidTestAppCloning.xml
deleted file mode 100644
index 03802f2..0000000
--- a/hostsidetests/scopedstorage/AndroidTestAppCloning.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT 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="Test for App cloning support with clone user profiles">
- <option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="framework" />
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
- <!-- TODO(b/169101565): change to secondary_user when fixed -->
- <!-- Clone user profile is meant to exist only alongside a real system user.
- It does not exist for a headless system user, or a secondary user -->
- <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
- <test class="com.android.tradefed.testtype.HostTest" >
- <option name="class" value="android.scopedstorage.cts.host.AppCloningHostTest" />
- </test>
-
- <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
- <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
- </object>
-</configuration>
diff --git a/hostsidetests/scopedstorage/CoreTest.xml b/hostsidetests/scopedstorage/CoreTest.xml
index 5b725e1..325807d 100644
--- a/hostsidetests/scopedstorage/CoreTest.xml
+++ b/hostsidetests/scopedstorage/CoreTest.xml
@@ -27,6 +27,12 @@
<option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
<option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
</target_preparer>
+
+ <option
+ name="config-descriptor:metadata"
+ key="mainline-param"
+ value="com.google.android.mediaprovider.apex" />
+
<test class="com.android.tradefed.testtype.HostTest" >
<option name="class" value="android.scopedstorage.cts.host.ScopedStorageCoreHostTest" />
</test>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
index a93aeee..ee63a8a 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
@@ -24,6 +24,7 @@
import static android.scopedstorage.cts.lib.TestUtils.CREATE_FILE_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.CREATE_IMAGE_ENTRY_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.DELETE_FILE_QUERY;
+import static android.scopedstorage.cts.lib.TestUtils.DELETE_RECURSIVE_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXCEPTION;
import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_CALLING_PKG;
import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_PATH;
@@ -40,6 +41,7 @@
import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.SETATTR_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.canOpen;
+import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
@@ -93,6 +95,7 @@
case CAN_READ_WRITE_QUERY:
case CREATE_FILE_QUERY:
case DELETE_FILE_QUERY:
+ case DELETE_RECURSIVE_QUERY:
case CAN_OPEN_FILE_FOR_READ_QUERY:
case CAN_OPEN_FILE_FOR_WRITE_QUERY:
case OPEN_FILE_FOR_READ_QUERY:
@@ -263,6 +266,9 @@
case DELETE_FILE_QUERY:
intent.putExtra(queryType, file.delete());
return intent;
+ case DELETE_RECURSIVE_QUERY:
+ intent.putExtra(queryType, deleteRecursively(file));
+ return intent;
case SETATTR_QUERY:
int newTimeMillis = 12345000;
intent.putExtra(queryType, file.setLastModified(newTimeMillis));
diff --git a/hostsidetests/scopedstorage/device/AndroidTest.xml b/hostsidetests/scopedstorage/device/AndroidTest.xml
index 7e6f895..5730b2e 100644
--- a/hostsidetests/scopedstorage/device/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/device/AndroidTest.xml
@@ -23,6 +23,11 @@
<option name="test-file-name" value="CtsScopedStorageTestAppFileManager.apk" />
</target_preparer>
+ <option
+ name="config-descriptor:metadata"
+ key="mainline-param"
+ value="com.google.android.mediaprovider.apex" />
+
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/RedactUriDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/RedactUriDeviceTest.java
index f0be82f..2e8ccda 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/RedactUriDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/RedactUriDeviceTest.java
@@ -58,7 +58,6 @@
import org.junit.AfterClass;
import org.junit.BeforeClass;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -272,7 +271,6 @@
* redacted mode.
**/
@Test
- @Ignore("Enable when b/194700183 is fixed")
public void testSharedRedactedUri_openFileForRead() throws Exception {
forceStopApp(APP_B_NO_PERMS.getPackageName());
final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME);
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
index 605f85c..756e425 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -29,8 +29,10 @@
import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories;
import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameDirectory;
import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories;
import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
import static android.scopedstorage.cts.lib.TestUtils.assertMountMode;
@@ -43,6 +45,7 @@
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
+import static android.scopedstorage.cts.lib.TestUtils.deleteRecursivelyAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProvider;
import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
@@ -85,8 +88,10 @@
import static android.scopedstorage.cts.lib.TestUtils.revokePermission;
import static android.scopedstorage.cts.lib.TestUtils.setAppOpsModeForUid;
import static android.scopedstorage.cts.lib.TestUtils.setAttrAs;
+import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert;
import static android.scopedstorage.cts.lib.TestUtils.uninstallApp;
import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert;
import static android.scopedstorage.cts.lib.TestUtils.updateDisplayNameWithMediaProvider;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaRelativePath_denied;
@@ -111,6 +116,7 @@
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import android.Manifest;
@@ -206,7 +212,7 @@
"CtsScopedStorageTestAppFileManager.apk");
// A legacy targeting app with RES and WES permissions
private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy",
- "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
+ "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppDLegacy.apk");
// The following apps are not installed at test startup - please install before using.
private static final TestApp APP_C = new TestApp("TestAppC",
@@ -520,7 +526,7 @@
public void testCreateAndDeleteEmptyDir() throws Exception {
final File externalFilesDir = getExternalFilesDir();
// Remove directory in order to create it again
- externalFilesDir.delete();
+ deleteRecursively(externalFilesDir);
// Can create own external files dir
assertThat(externalFilesDir.mkdir()).isTrue();
@@ -534,9 +540,9 @@
assertThat(dir2.mkdir()).isTrue();
// And can delete them all
- assertThat(dir2.delete()).isTrue();
- assertThat(dir1.delete()).isTrue();
- assertThat(externalFilesDir.delete()).isTrue();
+ assertThat(deleteRecursively(dir2)).isTrue();
+ assertThat(deleteRecursively(dir1)).isTrue();
+ assertThat(deleteRecursively(externalFilesDir)).isTrue();
// Can't create external dir for other apps
final File nonexistentPackageFileDir = new File(
@@ -614,7 +620,7 @@
// At this point, we're not sure who created this file, so we'll have both apps
// deleting it
mediaFile.delete();
- dirInDownload.delete();
+ deleteRecursively(dirInDownload);
}
}
@@ -751,7 +757,7 @@
assertThat(dir.list()).asList().doesNotContain(videoFileName);
} finally {
deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getPath());
- dir.delete();
+ deleteRecursively(dir);
}
}
@@ -783,7 +789,7 @@
assertThat(listAs(APP_A_HAS_RES, dir.getPath())).doesNotContain(pdfFileName);
} finally {
deleteFileAsNoThrow(APP_B_NO_PERMS, pdfFile.getPath());
- dir.delete();
+ deleteRecursively(dir);
}
}
@@ -1075,6 +1081,61 @@
}
}
+ void writeAndCheckMtime(final boolean append) throws Exception {
+ File file = new File(getDcimDir(), "update_modifies_mtime.jpg");
+
+ try {
+ assertThat(file.createNewFile()).isTrue();
+ assertThat(file.exists()).isTrue();
+
+ final long creationTime = file.lastModified();
+
+ // File should exist
+ assertNotEquals(creationTime, 0L);
+
+ // Sleep a bit more than 1 second because although
+ // File::lastModified() represents the duration in milliseconds,
+ // has 1 second precision.
+ // With lower sleep durations the test results flakey...
+ Thread.sleep(2000);
+
+ // Modification time should be the same as long the file has not
+ // been modified
+ assertEquals(creationTime, file.lastModified());
+
+ // Sleep a bit more than 1 second because although
+ // File::lastModified() represents the duration in milliseconds,
+ // has 1 second precision.
+ // With lower sleep durations the test results flakey...
+ Thread.sleep(2000);
+
+ // Assert we can write to the file
+ try (FileOutputStream fos = new FileOutputStream(file, append)) {
+ fos.write(BYTES_DATA1);
+ fos.close();
+ }
+
+ final long modificationTime = file.lastModified();
+
+ // As the file has been written, modification time should have
+ // changed
+ assertNotEquals(modificationTime, 0L);
+ assertNotEquals(modificationTime, creationTime);
+ } finally {
+ file.delete();
+ }
+ }
+
+ @Test
+ public void testAppendUpdatesMtime() throws Exception {
+ writeAndCheckMtime(true);
+ }
+
+ @Test
+ public void testWriteUpdatesMtime() throws Exception {
+ writeAndCheckMtime(false);
+ }
+
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testDefaultNoIsolatedStorageFlag() throws Exception {
@@ -1157,7 +1218,7 @@
try {
// Delete the directory if it already exists
if (podcastsDir.exists()) {
- deleteAsLegacyApp(podcastsDir);
+ deleteRecursivelyAsLegacyApp(podcastsDir);
}
assertThat(podcastsDir.exists()).isFalse();
assertThat(podcastsDirLowerCase.exists()).isFalse();
@@ -1578,7 +1639,7 @@
videoFile1.delete();
videoFile2.delete();
videoFile3.delete();
- nonMediaDir.delete();
+ deleteRecursively(nonMediaDir);
}
}
@@ -1754,15 +1815,15 @@
} finally {
pdfFile.delete();
- nonMediaDirectory.delete();
+ deleteRecursively(nonMediaDirectory);
videoFile1.delete();
videoFile2.delete();
videoFile3.delete();
- mediaDirectory1.delete();
- mediaDirectory2.delete();
- mediaDirectory3.delete();
- mediaDirectory4.delete();
+ deleteRecursively(mediaDirectory1);
+ deleteRecursively(mediaDirectory2);
+ deleteRecursively(mediaDirectory3);
+ deleteRecursively(mediaDirectory4);
}
}
@@ -1788,7 +1849,8 @@
assertThat(deleteFileAs(APP_B_NO_PERMS, videoFile.getAbsolutePath())).isTrue();
} finally {
deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getAbsolutePath());
- mediaDirectory1.delete();
+ deleteRecursively(mediaDirectory1);
+ deleteRecursively(mediaDirectory2);
}
}
@@ -1807,8 +1869,8 @@
assertThat(emptyDirectoryOldPath.mkdirs()).isTrue();
assertCanRenameDirectory(emptyDirectoryOldPath, emptyDirectoryNewPath, null, null);
} finally {
- emptyDirectoryOldPath.delete();
- emptyDirectoryNewPath.delete();
+ deleteRecursively(emptyDirectoryOldPath);
+ deleteRecursively(emptyDirectoryNewPath);
}
}
@@ -1932,8 +1994,8 @@
} finally {
hiddenImageFile.delete();
imageFile.delete();
- hiddenDir.delete();
- nonHiddenDir.delete();
+ deleteRecursively(hiddenDir);
+ deleteRecursively(nonHiddenDir);
}
}
@@ -1972,7 +2034,7 @@
noMediaFile.delete();
imageFile.delete();
videoFile.delete();
- directoryNoMedia.delete();
+ deleteRecursively(directoryNoMedia);
}
}
@@ -2057,7 +2119,7 @@
// file.
assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true);
- trashFile(imageFileUri);
+ trashFileAndAssert(imageFileUri);
// Check that only owner package, file manager and system gallery can list trashed image
// file.
assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true);
@@ -2066,7 +2128,7 @@
// Check that only owner package, file manager can list pending non media file.
assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false);
- trashFile(pdfFileUri);
+ trashFileAndAssert(pdfFileUri);
// Check that only owner package, file manager can list trashed non media file.
assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false);
} finally {
@@ -2198,6 +2260,65 @@
}
@Test
+ public void testSystemGalleryCanTrashOtherAndroidMediaFiles() throws Exception {
+ final File otherVideoFile = new File(getAndroidMediaDir(),
+ String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME));
+ try {
+ allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+ assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue();
+
+ final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile);
+ assertNotNull(otherVideoUri);
+
+ trashFileAndAssert(otherVideoUri);
+ untrashFileAndAssert(otherVideoUri);
+ } finally {
+ otherVideoFile.delete();
+ denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+ }
+ }
+
+ @Test
+ public void testSystemGalleryCanUpdateOtherAndroidMediaFiles() throws Exception {
+ final File otherImageFile = new File(getAndroidMediaDir(),
+ String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), IMAGE_FILE_NAME));
+ final File updatedImageFileInDcim = new File(getDcimDir(), IMAGE_FILE_NAME);
+ try {
+ allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+ assertThat(createFileAs(APP_B_NO_PERMS, otherImageFile.getAbsolutePath())).isTrue();
+
+ final Uri otherImageUri = MediaStore.scanFile(getContentResolver(), otherImageFile);
+ assertNotNull(otherImageUri);
+
+ final ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
+ // Test that we can move the file to "DCIM/"
+ assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+ + " with values " + values)
+ .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+ .isEqualTo(1);
+ assertThat(updatedImageFileInDcim.exists()).isTrue();
+ assertThat(otherImageFile.exists()).isFalse();
+
+ values.clear();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH,
+ "Android/media/" + APP_B_NO_PERMS.getPackageName());
+ // Test that we can move the file back to other app's owned path
+ assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+ + " with values " + values)
+ .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+ .isEqualTo(1);
+ assertThat(otherImageFile.exists()).isTrue();
+ } finally {
+ otherImageFile.delete();
+ updatedImageFileInDcim.delete();
+ denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+ }
+ }
+
+ @Test
public void testQueryOtherAppsFiles() throws Exception {
final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
@@ -2291,8 +2412,8 @@
otherAppVideoFile2.delete();
otherAppPdfFile1.delete();
otherAppPdfFile2.delete();
- dirInDcim.delete();
- dirInPictures.delete();
+ deleteRecursively(dirInDcim);
+ deleteRecursively(dirInPictures);
denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
}
}
@@ -2416,8 +2537,8 @@
fileSpecialChars.delete();
fileSpecialChars1.delete();
fileSpecialChars2.delete();
- dirSpecialChars.delete();
- renamedDir.delete();
+ deleteRecursively(dirSpecialChars);
+ deleteRecursively(renamedDir);
}
}
@@ -2487,7 +2608,7 @@
} finally {
deleteAsLegacyApp(topLevelDir1);
deleteAsLegacyApp(topLevelDir2);
- nonTopLevelDir.delete();
+ deleteRecursively(nonTopLevelDir);
}
}
@@ -2725,18 +2846,57 @@
}
}
+ /**
+ * Tests that System Gallery apps cannot insert files in other app's private directories.
+ */
+ @Test
+ public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+ int uid = Process.myUid();
+ try {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+ assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ } finally {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+ }
+ }
+
+ /**
+ * Tests that System Gallery apps cannot update files in other app's private directories.
+ */
+ @Test
+ public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+ int uid = Process.myUid();
+ try {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+ assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ } finally {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+ }
+ }
+
+ /**
+ * This test is for operations to the calling app's own private packages.
+ */
@Test
public void testInsertFromExternalDirsViaRelativePath() throws Exception {
verifyInsertFromExternalMediaDirViaRelativePath_allowed();
verifyInsertFromExternalPrivateDirViaRelativePath_denied();
}
+ /**
+ * This test is for operations to the calling app's own private packages.
+ */
@Test
public void testUpdateToExternalDirsViaRelativePath() throws Exception {
verifyUpdateToExternalMediaDirViaRelativePath_allowed();
verifyUpdateToExternalPrivateDirsViaRelativePath_denied();
}
+ /**
+ * This test is for operations to the calling app's own private packages.
+ */
@Test
public void testInsertFromExternalDirsViaRelativePathAsSystemGallery() throws Exception {
int uid = Process.myUid();
@@ -2749,6 +2909,9 @@
}
}
+ /**
+ * This test is for operations to the calling app's own private packages.
+ */
@Test
public void testUpdateToExternalDirsViaRelativePathAsSystemGallery() throws Exception {
int uid = Process.myUid();
@@ -2999,16 +3162,10 @@
final Uri trashedFileUri = MediaStore.scanFile(cr, trashedFile);
assertNotNull(trashedFileUri);
- trashFile(trashedFileUri);
+ trashFileAndAssert(trashedFileUri);
return trashedFileUri;
}
- private void trashFile(Uri uri) throws Exception {
- final ContentValues values = new ContentValues();
- values.put(MediaStore.MediaColumns.IS_TRASHED, 1);
- assertEquals(1, getContentResolver().update(uri, values, Bundle.EMPTY));
- }
-
/**
* Gets file path corresponding to the db row pointed by {@code uri}. If {@code uri} points to
* multiple db rows, file path is extracted from the first db row of the database query result.
@@ -3281,4 +3438,14 @@
Log.d(TAG, "Deleting file " + file);
deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath());
}
+
+ /**
+ * Deletes the given file/directory recursively. If the file is a directory, then deletes all
+ * of its children (files or directories) recursively.
+ */
+ private void deleteRecursivelyAsLegacyApp(File dir) throws Exception {
+ // Use a legacy app to delete this directory, since it could be outside shared storage.
+ Log.d(TAG, "Deleting directory " + dir);
+ deleteRecursivelyAs(APP_D_LEGACY_HAS_RW, dir.getAbsolutePath());
+ }
}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/AppCloningHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/AppCloningHostTest.java
deleted file mode 100644
index 281df8f..0000000
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/AppCloningHostTest.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.scopedstorage.cts.host;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
-
-import android.platform.test.annotations.AppModeFull;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.contentprovider.ContentProviderHandler;
-import com.android.tradefed.targetprep.TargetSetupError;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.util.CommandResult;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Runs the AppCloning tests.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-@AppModeFull
-public class AppCloningHostTest extends BaseHostTestCase {
- private static final String APP_A = "CtsScopedStorageTestAppA.apk";
- private static final String APP_A_PACKAGE = "android.scopedstorage.cts.testapp.A.withres";
- private static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
- private static final int CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS = 20000;
- private String mCloneUserId;
- private ContentProviderHandler mContentProviderHandler;
-
-
- @Before
- public void setup() throws Exception {
- assumeFalse("Device is in headless system user mode", isHeadlessSystemUserMode());
- assumeTrue(isAtLeastS());
-
- String output = executeShellCommand(
- "pm create-user --profileOf 0 --user-type android.os.usertype.profile.CLONE "
- + "testUser");
- mCloneUserId = output.substring(output.lastIndexOf(' ') + 1).replaceAll("[^0-9]", "");
- assertThat(mCloneUserId).isNotEmpty();
- CommandResult out = executeShellV2Command("am start-user -w %s", mCloneUserId);
- assertThat(out.getStderr()).isEmpty();
- mContentProviderHandler = new ContentProviderHandler(getDevice());
- mContentProviderHandler.setUp();
- }
-
- @After
- public void tearDown() throws Exception {
- if (isHeadlessSystemUserMode() || !isAtLeastS()) return;
- mContentProviderHandler.tearDown();
- executeShellCommand("pm remove-user %s", mCloneUserId);
- }
-
- @Test
- public void testInstallAppTwice() throws Exception {
- installAppAsUser(APP_A, getCurrentUserId());
- installAppAsUser(APP_A, Integer.valueOf(mCloneUserId));
- uninstallPackage(APP_A_PACKAGE);
- }
-
- @Test
- public void testCreateCloneUserFile() throws Exception {
- CommandResult out;
-
- // Check that the clone user directories exist
- eventually(() -> {
- // Wait for finish.
- assertThat(isSuccessful(
- runContentProviderCommand("query", mCloneUserId, "/sdcard", ""))).isTrue();
- }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
-
- // Create a file on the clone user storage
- out = executeShellV2Command("touch /sdcard/testFile.txt");
- assertThat(isSuccessful(out)).isTrue();
- eventually(() -> {
- // Wait for finish.
- assertThat(isSuccessful(
- runContentProviderCommand("write", mCloneUserId, "/sdcard/testFile.txt",
- "< /sdcard/testFile.txt"))).isTrue();
- }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
-
- // Check that the above created file exists on the clone user storage
- out = runContentProviderCommand("query", mCloneUserId, "/sdcard/testFile.txt", "");
- assertThat(isSuccessful(out)).isTrue();
-
- // Cleanup the created file
- out = runContentProviderCommand("delete", mCloneUserId, "/sdcard/testFile.txt", "");
- assertThat(isSuccessful(out)).isTrue();
- }
-
- @Test
- public void testPrivateAppDataDirectoryForCloneUser() throws Exception {
- installAppAsUser(APP_A, Integer.valueOf(mCloneUserId));
- eventually(() -> {
- // Wait for finish.
- assertThat(isPackageInstalled(APP_A_PACKAGE, mCloneUserId)).isTrue();
- }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
- }
-
- private void installAppAsUser(String packageFile, int userId)
- throws TargetSetupError, DeviceNotAvailableException {
- installPackageAsUser(packageFile, false, userId, "-t");
- }
-
- private CommandResult runContentProviderCommand(String commandType, String userId,
- String relativePath, String args) throws Exception {
- String fullUri = CONTENT_PROVIDER_URL + relativePath;
- return executeShellV2Command("content %s --user %s --uri %s %s",
- commandType, userId, fullUri, args);
- }
-
-}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
index 3b81646..5638e41 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
@@ -111,6 +111,46 @@
}
@Test
+ public void testCantInsertFilesInOtherAppPrivateDir_hasRW() throws Exception {
+ runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasRW");
+ }
+
+ @Test
+ public void testCantUpdateFilesInOtherAppPrivateDir_hasRW() throws Exception {
+ runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasRW");
+ }
+
+ @Test
+ public void testCantInsertFilesInOtherAppPrivateDir_hasMES() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasMES");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ @Test
+ public void testCantUpdateFilesInOtherAppPrivateDir_hasMES() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasMES");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ @Test
+ public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+ runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery");
+ }
+
+ @Test
+ public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+ runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery");
+ }
+
+ @Test
public void testMkdirInRandomPlaces_hasW() throws Exception {
revokePermissions("android.permission.READ_EXTERNAL_STORAGE");
executeShellCommand("mkdir -p /sdcard/Android/data/com.android.shell -m 2770");
@@ -147,6 +187,11 @@
}
@Test
+ public void testCanTrashOtherAndroidMediaFiles_hasRW() throws Exception {
+ runDeviceTest("testCanTrashOtherAndroidMediaFiles_hasRW");
+ }
+
+ @Test
public void testCantRename_hasR() throws Exception {
revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
runDeviceTest("testCantRename_hasR");
@@ -214,6 +259,15 @@
runDeviceTest("testLegacySystemGalleryCanRenameImagesAndVideosWithoutDbUpdates");
}
+ /**
+ * (b/205673506): Test that legacy System Gallery can update() media file's releative_path to a
+ * non default top level directory.
+ */
+ @Test
+ public void testLegacySystemGalleryCanUpdateToExistingDirectory() throws Exception {
+ runDeviceTest("testLegacySystemGalleryCanUpdateToExistingDirectory");
+ }
+
@Test
public void testLegacySystemGalleryWithoutWESCannotRename() throws Exception {
revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
@@ -249,4 +303,18 @@
public void testUpdateToExternalDirsViaRelativePath() throws Exception {
runDeviceTest("testUpdateToExternalDirsViaRelativePath");
}
+
+ private void allowAppOps(String... ops) throws Exception {
+ for (String op : ops) {
+ executeShellCommand("cmd appops set --uid android.scopedstorage.cts.legacy "
+ + op + " allow");
+ }
+ }
+
+ private void denyAppOps(String... ops) throws Exception {
+ for (String op : ops) {
+ executeShellCommand("cmd appops set --uid android.scopedstorage.cts.legacy "
+ + op + " deny");
+ }
+ }
}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
index f1236b6..cd9378d 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -125,6 +125,26 @@
}
@Test
+ public void testManageExternalStorageCantInsertFilesInOtherAppPrivateDir() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testManageExternalStorageCantInsertFilesInOtherAppPrivateDir");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ @Test
+ public void testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ @Test
public void testCheckInstallerAppAccessToObbDirs() throws Exception {
allowAppOps("android:request_install_packages");
grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE");
@@ -179,6 +199,26 @@
}
@Test
+ public void testFileManagerCanTrashOtherAndroidMediaFiles() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testFileManagerCanTrashOtherAndroidMediaFiles");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ @Test
+ public void testFileManagerCanUpdateOtherAndroidMediaFiles() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testFileManagerCanUpdateOtherAndroidMediaFiles");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ @Test
public void testOpenOtherPendingFilesFromFuse() throws Exception {
grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
try {
diff --git a/hostsidetests/scopedstorage/legacy/AndroidManifest.xml b/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
index c602f0a..c85b090 100644
--- a/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
+++ b/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application android:requestLegacyExternalStorage="true" >
<uses-library android:name="android.test.runner" />
diff --git a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
index 4754d74..40c9063 100644
--- a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
+++ b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
@@ -23,7 +23,9 @@
import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories;
import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories;
import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
import static android.scopedstorage.cts.lib.TestUtils.canOpenFileAs;
@@ -31,6 +33,7 @@
import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
import static android.scopedstorage.cts.lib.TestUtils.createImageEntryAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
@@ -38,6 +41,7 @@
import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
import static android.scopedstorage.cts.lib.TestUtils.getDcimDir;
import static android.scopedstorage.cts.lib.TestUtils.getExternalFilesDir;
+import static android.scopedstorage.cts.lib.TestUtils.getExternalStorageDir;
import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase;
import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
@@ -46,9 +50,13 @@
import static android.scopedstorage.cts.lib.TestUtils.insertFileFromExternalMedia;
import static android.scopedstorage.cts.lib.TestUtils.listAs;
import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
+import static android.scopedstorage.cts.lib.TestUtils.pollForManageExternalStorageAllowed;
import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
import static android.scopedstorage.cts.lib.TestUtils.resetDefaultExternalStorageVolume;
+import static android.scopedstorage.cts.lib.TestUtils.setAppOpsModeForUid;
import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
+import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert;
+import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert;
import static android.scopedstorage.cts.lib.TestUtils.updateFile;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
@@ -59,6 +67,7 @@
import static androidx.test.InstrumentationRegistry.getContext;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -126,12 +135,14 @@
* test runs.
*/
static final String NONCE = String.valueOf(System.nanoTime());
- static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
+ static final String TEST_DIRECTORY_NAME = "ScopedStorageTestDirectory" + NONCE;
static final String IMAGE_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".jpg";
static final String VIDEO_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".mp4";
static final String NONMEDIA_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".pdf";
+ static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
+
// The following apps are installed before the tests are run via a target_preparer.
// See test config for details.
// An app with READ_EXTERNAL_STORAGE permission
@@ -341,7 +352,7 @@
try {
assertThat(newDir.mkdir()).isFalse();
} finally {
- newDir.delete();
+ deleteRecursively(newDir);
}
}
@@ -426,8 +437,25 @@
pdfFile1.delete();
pdfFile2.delete();
- nonMediaDir1.delete();
- nonMediaDir2.delete();
+ deleteRecursively(nonMediaDir1);
+ deleteRecursively(nonMediaDir2);
+ }
+ }
+
+ @Test
+ public void testCanTrashOtherAndroidMediaFiles_hasRW() throws Exception {
+ final File otherVideoFile = new File(getAndroidMediaDir(),
+ String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME));
+ try {
+ assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue();
+
+ final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile);
+ assertNotNull(otherVideoUri);
+
+ trashFileAndAssert(otherVideoUri);
+ untrashFileAndAssert(otherVideoUri);
+ } finally {
+ otherVideoFile.delete();
}
}
@@ -521,8 +549,8 @@
// UNIQUE constraint error.
TestUtils.renameWithMediaProvider(directoryOldPath, directoryNewPath);
} finally {
- directoryOldPath.delete();
- directoryNewPath.delete();
+ deleteRecursively(directoryOldPath);
+ deleteRecursively(directoryNewPath);
}
}
@@ -698,7 +726,7 @@
imageInNoMediaDir.delete();
renamedImageInDCIM.delete();
noMediaFile.delete();
- directoryNoMedia.delete();
+ deleteRecursively(directoryNoMedia);
}
}
@@ -852,6 +880,41 @@
}
}
+ /**
+ * (b/205673506): Test that legacy System Gallery can update() media file's releative_path to a
+ * non default top level directory.
+ */
+ @Test
+ public void testLegacySystemGalleryCanUpdateToExistingDirectory() throws Exception {
+ pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+ final File imageFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+ // Top level non default directory
+ final File topLevelTestDirectory = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME);
+ final File imageFileInTopLevelDir = new File(topLevelTestDirectory, IMAGE_FILE_NAME);
+ try {
+ assertThat(imageFile.createNewFile()).isTrue();
+ final Uri imageUri = MediaStore.scanFile(getContentResolver(), imageFile);
+ assertThat(imageUri).isNotNull();
+
+ topLevelTestDirectory.mkdirs();
+ assertThat(topLevelTestDirectory.exists()).isTrue();
+
+ allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH, topLevelTestDirectory.getName());
+ final int result = getContentResolver().update(imageUri, values, Bundle.EMPTY);
+ assertWithMessage("Result of update() from DCIM -> top level test directory")
+ .that(result).isEqualTo(1);
+ assertThat(imageFileInTopLevelDir.exists()).isTrue();
+ } finally {
+ imageFile.delete();
+ imageFileInTopLevelDir.delete();
+ deleteRecursively(topLevelTestDirectory);
+ denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+ }
+ }
+
@Test
public void testLegacySystemGalleryWithoutWESCannotRename() throws Exception {
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
@@ -939,6 +1002,82 @@
}
/**
+ * Tests that legacy apps cannot insert in other app private directory
+ */
+ @Test
+ public void testCantInsertFilesInOtherAppPrivateDir_hasRW() throws Exception {
+ pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /* granted */ true);
+ pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /* granted */ true);
+
+ assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ }
+
+ /**
+ * Tests that legacy apps cannot update in other app private directory
+ */
+ @Test
+ public void testCantUpdateFilesInOtherAppPrivateDir_hasRW() throws Exception {
+ pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /* granted */ true);
+ pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /* granted */ true);
+
+ TestUtils.assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ }
+
+ /**
+ * Tests that legacy apps with MANAGE_EXTERNAL_STORAGE cannot insert in other app private
+ * directory
+ */
+ @Test
+ public void testCantInsertFilesInOtherAppPrivateDir_hasMES() throws Exception {
+ pollForManageExternalStorageAllowed();
+ assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ }
+
+ /**
+ * Tests that legacy apps with MANAGE_EXTERNAL_STORAGE cannot update in other app private
+ * directory
+ */
+ @Test
+ public void testCantUpdateFilesInOtherAppPrivateDir_hasMES() throws Exception {
+ pollForManageExternalStorageAllowed();
+ assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ }
+
+ /**
+ * Tests that legacy System Gallery apps cannot insert in other app private directory
+ */
+ @Test
+ public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+ int uid = Process.myUid();
+ try {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+ assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ } finally {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+ }
+ }
+
+ /**
+ * Tests that legacy System Gallery apps cannot update in other app private directory
+ */
+ @Test
+ public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+ int uid = Process.myUid();
+ try {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+ assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ } finally {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+ }
+ }
+
+ /**
* Make sure inserting files from app private directories in legacy apps is allowed via DATA.
*/
@Test
@@ -947,7 +1086,7 @@
ContentValues values = new ContentValues();
final String androidObbDir =
- getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+ TestUtils.getExternalObbDir().toString() + "/" + System.currentTimeMillis();
values.put(MediaStore.MediaColumns.DATA, androidObbDir);
insertFile(values);
@@ -981,7 +1120,7 @@
assertNotEquals(0, updateFile(uri, values));
final String androidObbDir =
- getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+ TestUtils.getExternalObbDir().toString() + "/" + System.currentTimeMillis();
values.put(MediaStore.MediaColumns.DATA, androidObbDir);
assertNotEquals(0, updateFile(uri, values));
diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
index 041e5ce..314dbb4 100644
--- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
+++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
@@ -16,6 +16,7 @@
package android.scopedstorage.cts.lib;
+import static android.provider.MediaStore.VOLUME_EXTERNAL;
import static android.scopedstorage.cts.lib.RedactionTestHelper.EXIF_METADATA_QUERY;
import static androidx.test.InstrumentationRegistry.getContext;
@@ -98,6 +99,7 @@
public static final String CREATE_IMAGE_ENTRY_QUERY =
"android.scopedstorage.cts.createimageentry";
public static final String DELETE_FILE_QUERY = "android.scopedstorage.cts.deletefile";
+ public static final String DELETE_RECURSIVE_QUERY = "android.scopedstorage.cts.deleteRecursive";
public static final String CAN_OPEN_FILE_FOR_READ_QUERY =
"android.scopedstorage.cts.can_openfile_read";
public static final String CAN_OPEN_FILE_FOR_WRITE_QUERY =
@@ -295,6 +297,17 @@
}
/**
+ * Makes the given {@code testApp} delete a file or directory.
+ * If the file is a directory, then deletes all of its children (file or directories)
+ * recursively.
+ *
+ * <p>This method drops shell permission identity.
+ */
+ public static boolean deleteRecursivelyAs(TestApp testApp, String path) throws Exception {
+ return getResultFromTestApp(testApp, path, DELETE_RECURSIVE_QUERY);
+ }
+
+ /**
* Makes the given {@code testApp} delete a file. Doesn't throw in case of failure.
*/
public static boolean deleteFileAsNoThrow(TestApp testApp, String path) {
@@ -417,10 +430,8 @@
}
public static void verifyInsertFromExternalPrivateDirViaRelativePath_denied() throws Exception {
- resetDefaultExternalStorageVolume();
-
// Test that inserting files from Android/obb/.. is not allowed.
- final String androidObbDir = getContext().getObbDir().toString();
+ final String androidObbDir = getExternalObbDir().toString();
ContentValues values = new ContentValues();
values.put(
MediaStore.MediaColumns.RELATIVE_PATH,
@@ -436,8 +447,6 @@
}
public static void verifyInsertFromExternalMediaDirViaRelativePath_allowed() throws Exception {
- resetDefaultExternalStorageVolume();
-
// Test that inserting files from Android/media/.. is allowed.
final String androidMediaDir = getExternalMediaDir().toString();
final ContentValues values = new ContentValues();
@@ -448,13 +457,11 @@
}
public static void verifyInsertFromExternalPrivateDirViaData_denied() throws Exception {
- resetDefaultExternalStorageVolume();
-
ContentValues values = new ContentValues();
// Test that inserting files from Android/obb/.. is not allowed.
final String androidObbDir =
- getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+ getExternalObbDir().toString() + "/" + System.currentTimeMillis();
values.put(MediaStore.MediaColumns.DATA, androidObbDir);
assertThrows(IllegalArgumentException.class, () -> insertFile(values));
@@ -465,8 +472,6 @@
}
public static void verifyInsertFromExternalMediaDirViaData_allowed() throws Exception {
- resetDefaultExternalStorageVolume();
-
// Test that inserting files from Android/media/.. is allowed.
ContentValues values = new ContentValues();
final String androidMediaDirFile =
@@ -477,7 +482,6 @@
// NOTE: While updating, DATA field should be ignored for all the apps including file manager.
public static void verifyUpdateToExternalDirsViaData_denied() throws Exception {
- resetDefaultExternalStorageVolume();
Uri uri = insertFileFromExternalMedia(false);
final String androidMediaDirFile =
@@ -487,7 +491,7 @@
assertEquals(0, updateFile(uri, values));
final String androidObbDir =
- getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+ getExternalObbDir().toString() + "/" + System.currentTimeMillis();
values.put(MediaStore.MediaColumns.DATA, androidObbDir);
assertEquals(0, updateFile(uri, values));
@@ -498,7 +502,6 @@
public static void verifyUpdateToExternalMediaDirViaRelativePath_allowed()
throws IOException {
- resetDefaultExternalStorageVolume();
Uri uri = insertFileFromExternalMedia(true);
// Test that update to files from Android/media/.. is allowed.
@@ -512,11 +515,10 @@
public static void verifyUpdateToExternalPrivateDirsViaRelativePath_denied()
throws Exception {
- resetDefaultExternalStorageVolume();
Uri uri = insertFileFromExternalMedia(true);
// Test that update to files from Android/obb/.. is not allowed.
- final String androidObbDir = getContext().getObbDir().toString();
+ final String androidObbDir = getExternalObbDir().toString();
ContentValues values = new ContentValues();
values.put(
MediaStore.MediaColumns.RELATIVE_PATH,
@@ -950,6 +952,111 @@
}
/**
+ * Assert that app cannot insert files in other app's private directories
+ *
+ * @param fileName name of the file
+ * @param throwsExceptionForDataValue Apps like System Gallery for which Data column is not
+ * respected, will not throw an Exception as the Data value is ignored.
+ * @param otherApp Other test app in whose external private directory we will attempt to insert
+ * @param callingPackageName Calling package name
+ */
+ public static void assertCantInsertToOtherPrivateAppDirectories(String fileName,
+ boolean throwsExceptionForDataValue, TestApp otherApp, String callingPackageName)
+ throws Exception {
+ // Create directory in which the device test will try to insert file to
+ final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
+ callingPackageName, otherApp.getPackageName()));
+ final File file = new File(otherAppExternalDataDir, fileName);
+ try {
+ assertThat(createFileAs(otherApp, file.getPath())).isTrue();
+
+ final ContentValues valuesWithData = new ContentValues();
+ valuesWithData.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath());
+ try {
+ Uri uri = getContentResolver().insert(
+ MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+ valuesWithData);
+
+ if (throwsExceptionForDataValue) {
+ fail("File insert expected to fail: " + file);
+ } else {
+ try (Cursor c = getContentResolver().query(uri, new String[]{
+ MediaStore.MediaColumns.DATA}, null, null)) {
+ assertThat(c.moveToFirst()).isTrue();
+ assertThat(c.getString(0)).isNotEqualTo(file.getAbsolutePath());
+ }
+ }
+ } catch (IllegalArgumentException expected) {
+ }
+
+ final ContentValues valuesWithRelativePath = new ContentValues();
+ final String path = file.getAbsolutePath();
+ valuesWithRelativePath.put(MediaStore.MediaColumns.RELATIVE_PATH,
+ path.substring(path.indexOf("Android")));
+ valuesWithRelativePath.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
+ try {
+ getContentResolver().insert(MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+ valuesWithRelativePath);
+ fail("File insert expected to fail: " + file);
+ } catch (IllegalArgumentException expected) {
+ }
+ } finally {
+ deleteFileAsNoThrow(otherApp, file.getPath());
+ }
+ }
+
+ /**
+ * Assert that app cannot update files in other app's private directories
+ *
+ * @param fileName name of the file
+ * @param throwsExceptionForDataValue Apps like non-legacy System Gallery/MES for which
+ * Data column is not respected, will not throw an Exception as the Data value is ignored.
+ * @param otherApp Other test app in whose external private directory we will attempt to insert
+ * @param callingPackageName Calling package name
+ */
+ public static void assertCantUpdateToOtherPrivateAppDirectories(String fileName,
+ boolean throwsExceptionForDataValue, TestApp otherApp, String callingPackageName)
+ throws Exception {
+ // Create priv-app file and add to the database that we will try to update
+ final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
+ callingPackageName, otherApp.getPackageName()));
+ final File file = new File(otherAppExternalDataDir, fileName);
+ try {
+ assertThat(createFileAs(otherApp, file.getPath())).isTrue();
+ MediaStore.scanFile(getContentResolver(), file);
+
+ final ContentValues valuesWithData = new ContentValues();
+ valuesWithData.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath());
+ try {
+ int res = getContentResolver().update(
+ MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+ valuesWithData, Bundle.EMPTY);
+
+ if (throwsExceptionForDataValue) {
+ fail("File update expected to fail: " + file);
+ } else {
+ assertThat(res).isEqualTo(0);
+ }
+ } catch (IllegalArgumentException expected) {
+ }
+
+ final ContentValues valuesWithRelativePath = new ContentValues();
+ final String path = file.getAbsolutePath();
+ valuesWithRelativePath.put(MediaStore.MediaColumns.RELATIVE_PATH,
+ path.substring(path.indexOf("Android")));
+ valuesWithRelativePath.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
+ try {
+ getContentResolver().update(MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+ valuesWithRelativePath, Bundle.EMPTY);
+ fail("File update expected to fail: " + file);
+ } catch (IllegalArgumentException expected) {
+ }
+ } finally {
+ deleteFileAsNoThrow(otherApp, file.getPath());
+ }
+ }
+
+ /**
* Asserts can rename directory.
*/
public static void assertCanRenameDirectory(File oldDirectory, File newDirectory,
@@ -1193,6 +1300,18 @@
}
/**
+ * Creates and returns the Android obb sub-directory belonging to the calling package.
+ */
+ public static File getExternalObbDir() {
+ final String packageName = getContext().getPackageName();
+ final File res = new File(getAndroidObbDir(), packageName);
+ if (!res.equals(getContext().getObbDirs()[0])) {
+ res.mkdirs();
+ }
+ return res;
+ }
+
+ /**
* Creates and returns the Android media sub-directory belonging to the calling package.
*/
public static File getExternalMediaDir() {
@@ -1272,6 +1391,10 @@
return new File(getAndroidDir(), "data");
}
+ public static File getAndroidObbDir() {
+ return new File(getAndroidDir(), "obb");
+ }
+
public static File getAndroidMediaDir() {
return new File(getAndroidDir(), "media");
}
@@ -1355,7 +1478,8 @@
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(actionName);
intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
- getContext().registerReceiver(broadcastReceiver, intentFilter);
+ getContext().registerReceiver(broadcastReceiver, intentFilter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
// Launch the test app.
intent.setPackage(testApp.getPackageName());
@@ -1738,4 +1862,21 @@
ActivityManager.class).getRunningAppProcesses().stream().filter(
p -> packageName.equals(p.processName)).findFirst();
}
+
+ public static void trashFileAndAssert(Uri uri) {
+ final ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.IS_TRASHED, 1);
+ assertWithMessage("Result of ContentResolver#update for " + uri + " with values to trash "
+ + "file " + values)
+ .that(getContentResolver().update(uri, values, Bundle.EMPTY)).isEqualTo(1);
+ }
+
+ public static void untrashFileAndAssert(Uri uri) {
+ final ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.IS_TRASHED, 0);
+ assertWithMessage("Result of ContentResolver#update for " + uri + " with values to untrash "
+ + "file " + values)
+ .that(getContentResolver().update(uri, values, Bundle.EMPTY)).isEqualTo(1);
+ }
+
}
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index c915235..ebc8f10 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -21,6 +21,8 @@
import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidDataDir;
import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidObbDir;
import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories;
import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
import static android.scopedstorage.cts.lib.TestUtils.assertMountMode;
@@ -30,6 +32,7 @@
import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
import static android.scopedstorage.cts.lib.TestUtils.dropShellPermissionIdentity;
import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
import static android.scopedstorage.cts.lib.TestUtils.getAndroidDir;
@@ -52,6 +55,8 @@
import static android.scopedstorage.cts.lib.TestUtils.pollForManageExternalStorageAllowed;
import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
+import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert;
+import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaData_denied;
@@ -66,6 +71,7 @@
import static androidx.test.InstrumentationRegistry.getContext;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -76,8 +82,10 @@
import android.Manifest;
import android.app.WallpaperManager;
+import android.content.ContentValues;
import android.net.Uri;
import android.os.Build;
+import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
@@ -122,6 +130,7 @@
static final String AUDIO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp3";
static final String IMAGE_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".jpg";
+ static final String VIDEO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp4";
static final String NONMEDIA_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".pdf";
// The following apps are installed before the tests are run via a target_preparer.
@@ -136,7 +145,7 @@
"CtsScopedStorageTestAppB.apk");
// A legacy targeting app with RES and WES permissions
private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy",
- "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
+ "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppDLegacy.apk");
@Before
public void setup() throws Exception {
@@ -222,6 +231,28 @@
});
}
+ /**
+ * Tests that apps with MANAGE_EXTERNAL_STORAGE permission cannot insert files in other app's
+ * private directories.
+ */
+ @Test
+ public void testManageExternalStorageCantInsertFilesInOtherAppPrivateDir() throws Exception {
+ pollForManageExternalStorageAllowed();
+ assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* throwsExceptionForDataValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ }
+
+ /**
+ * Tests that apps with MANAGE_EXTERNAL_STORAGE permission cannot update files in other app's
+ * private directories.
+ */
+ @Test
+ public void testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir() throws Exception {
+ pollForManageExternalStorageAllowed();
+ assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ }
+
@Test
public void testManageExternalStorageCanDeleteOtherAppsContents() throws Exception {
pollForManageExternalStorageAllowed();
@@ -299,7 +330,8 @@
final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
final File otherAppExternalDataSubDir = new File(otherAppExternalDataDir, "subdir");
- final File otherAppExternalDataFile = new File(otherAppExternalDataSubDir, "abc.jpg");
+ final File otherAppExternalDataFile =
+ new File(otherAppExternalDataSubDir, IMAGE_FILE_NAME);
assertThat(createFileAs(APP_B_NO_PERMS, otherAppExternalDataFile.getAbsolutePath()))
.isTrue();
@@ -489,7 +521,7 @@
nomediaFile.delete();
mediaFile.delete();
renamedMediaFile.delete();
- nomediaDir.delete();
+ deleteRecursively(nomediaDir);
}
}
@@ -526,14 +558,70 @@
mediaFile1InSubDir.delete();
mediaFile2InSubDir.delete();
topLevelNomediaFile.delete();
- nomediaSubDir.delete();
- nomediaDir.delete();
+ deleteRecursively(nomediaSubDir);
+ deleteRecursively(nomediaDir);
// Scan the directory to remove stale db rows.
MediaStore.scanFile(getContentResolver(), nomediaDir);
}
}
@Test
+ public void testFileManagerCanTrashOtherAndroidMediaFiles() throws Exception {
+ pollForManageExternalStorageAllowed();
+
+ final File otherVideoFile = new File(getAndroidMediaDir(),
+ String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME));
+ try {
+ assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue();
+
+ final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile);
+ assertNotNull(otherVideoUri);
+
+ trashFileAndAssert(otherVideoUri);
+ untrashFileAndAssert(otherVideoUri);
+ } finally {
+ otherVideoFile.delete();
+ }
+ }
+
+ @Test
+ public void testFileManagerCanUpdateOtherAndroidMediaFiles() throws Exception {
+ pollForManageExternalStorageAllowed();
+
+ final File otherImageFile = new File(getAndroidMediaDir(),
+ String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), IMAGE_FILE_NAME));
+ final File updatedImageFileInDcim = new File(getDcimDir(), IMAGE_FILE_NAME);
+ try {
+ assertThat(createFileAs(APP_B_NO_PERMS, otherImageFile.getAbsolutePath())).isTrue();
+
+ final Uri otherImageUri = MediaStore.scanFile(getContentResolver(), otherImageFile);
+ assertNotNull(otherImageUri);
+
+ final ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
+ // Test that we can move the file to "DCIM/"
+ assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+ + " with values " + values)
+ .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+ .isEqualTo(1);
+ assertThat(updatedImageFileInDcim.exists()).isTrue();
+ assertThat(otherImageFile.exists()).isFalse();
+
+ values.clear();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH,
+ "Android/media/" + APP_B_NO_PERMS.getPackageName());
+ // Test that we can move the file back to other app's owned path
+ assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+ + " with values " + values)
+ .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+ .isEqualTo(1);
+ } finally {
+ otherImageFile.delete();
+ updatedImageFileInDcim.delete();
+ }
+ }
+
+ @Test
public void testAndroidMedia() throws Exception {
// Check that the app does not have legacy external storage access
if (isAtLeastS()) {
@@ -818,8 +906,8 @@
imageFile.delete();
renamedImageFile.delete();
imageFileInRenamedDir.delete();
- dir.delete();
- renamedDir.delete();
+ deleteRecursively(dir);
+ deleteRecursively(renamedDir);
}
}
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
index 4bf8be0..ad9b58d 100755
--- a/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
@@ -79,6 +79,8 @@
case InputMessage::Type::MOTION: {
// int32_t eventId
outMsg->body.motion.eventId = msg.body.key.eventId;
+ // uint32_t pointerCount
+ outMsg->body.motion.pointerCount = msg.body.motion.pointerCount;
// nsecs_t eventTime
outMsg->body.motion.eventTime = msg.body.motion.eventTime;
// int32_t deviceId
@@ -125,14 +127,18 @@
outMsg->body.motion.xCursorPosition = msg.body.motion.xCursorPosition;
// float yCursorPosition
outMsg->body.motion.yCursorPosition = msg.body.motion.yCursorPosition;
- // uint32_t displayOrientation
- outMsg->body.motion.displayOrientation = msg.body.motion.displayOrientation;
- // int32_t displayW
- outMsg->body.motion.displayWidth = msg.body.motion.displayWidth;
- // int32_t displayH
- outMsg->body.motion.displayHeight = msg.body.motion.displayHeight;
- // uint32_t pointerCount
- outMsg->body.motion.pointerCount = msg.body.motion.pointerCount;
+ // float dsdxDisplay
+ outMsg->body.motion.dsdxRaw = msg.body.motion.dsdxRaw;
+ // float dtdxDisplay
+ outMsg->body.motion.dtdxRaw = msg.body.motion.dtdxRaw;
+ // float dtdyDisplay
+ outMsg->body.motion.dtdyRaw = msg.body.motion.dtdyRaw;
+ // float dsdyDisplay
+ outMsg->body.motion.dsdyRaw = msg.body.motion.dsdyRaw;
+ // float txDisplay
+ outMsg->body.motion.txRaw = msg.body.motion.txRaw;
+ // float tyDisplay
+ outMsg->body.motion.tyRaw = msg.body.motion.tyRaw;
//struct Pointer pointers[MAX_POINTERS]
for (size_t i = 0; i < msg.body.motion.pointerCount; i++) {
// PointerProperties properties
@@ -158,7 +164,6 @@
case InputMessage::Type::FOCUS: {
outMsg->body.focus.eventId = msg.body.focus.eventId;
outMsg->body.focus.hasFocus = msg.body.focus.hasFocus;
- outMsg->body.focus.inTouchMode = msg.body.focus.inTouchMode;
break;
}
case InputMessage::Type::CAPTURE: {
@@ -178,6 +183,10 @@
outMsg->body.timeline.graphicsTimeline = msg.body.timeline.graphicsTimeline;
break;
}
+ case InputMessage::Type::TOUCH_MODE: {
+ outMsg->body.touchMode.eventId = msg.body.timeline.eventId;
+ outMsg->body.touchMode.isInTouchMode = msg.body.touchMode.isInTouchMode;
+ }
}
}
@@ -251,9 +260,10 @@
}
InputMessage::Type types[] = {
- InputMessage::Type::KEY, InputMessage::Type::MOTION, InputMessage::Type::FINISHED,
- InputMessage::Type::FOCUS, InputMessage::Type::CAPTURE, InputMessage::Type::DRAG,
- InputMessage::Type::TIMELINE,
+ InputMessage::Type::KEY, InputMessage::Type::MOTION,
+ InputMessage::Type::FINISHED, InputMessage::Type::FOCUS,
+ InputMessage::Type::CAPTURE, InputMessage::Type::DRAG,
+ InputMessage::Type::TIMELINE, InputMessage::Type::TOUCH_MODE,
};
for (InputMessage::Type type : types) {
bool success = checkMessage(*server, *client, type);
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.bp
index e959c3a..a2f7e2f 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.bp
@@ -21,6 +21,7 @@
defaults: ["cts_hostsidetests_securitybulletin_defaults"],
srcs: ["poc.cpp"],
shared_libs: [
+ "android.media.audio.common.types-V1-cpp",
"audioclient-types-aidl-cpp",
"audioflinger-aidl-cpp",
"libmedia",
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2044/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2044/Android.bp
index 3638d08..0f49af7 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2044/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2044/Android.bp
@@ -30,8 +30,8 @@
"-DCHECK_OVERFLOW",
],
compile_multilib: "32",
- include_dirs: [
- "frameworks/av/media/libstagefright/rtsp/",
+ header_libs: [
+ "libstagefright_rtsp_headers",
],
shared_libs: [
"libmediaplayerservice",
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2044/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2044/poc.cpp
index e733c6f..ad4c8b8 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2044/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2044/poc.cpp
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#include <media/stagefright/rtsp/APacketSource.h>
+#include <media/stagefright/rtsp/ASessionDescription.h>
#include <stdlib.h>
-#include <APacketSource.h>
-#include <ASessionDescription.h>
using namespace android;
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/Android.bp
index 807b9106..877a566 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/Android.bp
@@ -14,6 +14,7 @@
* limitations under the License.
*
*/
+
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java
index 810f92d..c9c1d61 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java
@@ -17,9 +17,10 @@
package android.security.cts;
import android.platform.test.annotations.AsbSecurityTest;
+
import com.android.compatibility.common.util.CrashUtils;
-import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,10 +38,10 @@
assumeIsSupportedNfcDevice(getDevice());
pocPusher.only64();
String binaryName = "CVE-2018-9558";
- String signals[] = {CrashUtils.SIGABRT};
+ String[] signals = {CrashUtils.SIGABRT};
AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName);
- testConfig.config.setSignals(signals);
+ testConfig.config.setSignals(CrashUtils.SIGABRT);
AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
}
}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java
index 2c39674..64fed4f 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java
@@ -16,13 +16,13 @@
package android.security.cts;
-import com.android.tradefed.device.ITestDevice;
-import com.android.compatibility.common.util.CrashUtils;
-
import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.compatibility.common.util.CrashUtils;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
import org.junit.Test;
import org.junit.runner.RunWith;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@RunWith(DeviceJUnit4ClassRunner.class)
public class CVE_2020_0073 extends SecurityTestCase {
@@ -38,7 +38,7 @@
assumeIsSupportedNfcDevice(getDevice());
pocPusher.only64();
String binaryName = "CVE-2020-0073";
- String signals[] = {CrashUtils.SIGABRT};
+ String[] signals = {CrashUtils.SIGABRT};
AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName);
testConfig.config.setSignals(signals);
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java
index e4878a0..3e613aa 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java
@@ -17,9 +17,11 @@
package android.security.cts;
import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
import org.junit.Test;
import org.junit.runner.RunWith;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@RunWith(DeviceJUnit4ClassRunner.class)
public class CVE_2021_0430 extends SecurityTestCase {
diff --git a/hostsidetests/settings/Android.bp b/hostsidetests/settings/Android.bp
index 66a198b..0b5bd4a 100644
--- a/hostsidetests/settings/Android.bp
+++ b/hostsidetests/settings/Android.bp
@@ -30,7 +30,6 @@
],
// tag this module as a cts test artifact
test_suites: [
- "arcts",
"cts",
"general-tests",
],
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/Android.bp b/hostsidetests/settings/app/DeviceOwnerApp/Android.bp
index ec63294..7e1cd74 100644
--- a/hostsidetests/settings/app/DeviceOwnerApp/Android.bp
+++ b/hostsidetests/settings/app/DeviceOwnerApp/Android.bp
@@ -42,7 +42,6 @@
],
// tag this module as a cts test artifact
test_suites: [
- "arcts",
"cts",
"general-tests",
],
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
index 19f2b58..595e1ad 100644
--- 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
@@ -316,7 +316,8 @@
latch.countDown();
}
};
- getContext().registerReceiver(onResult, myFilter);
+ getContext().registerReceiver(onResult, myFilter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
assertTrue(getManager().requestPinShortcut(ms2,
PendingIntent.getBroadcast(getContext(), 0, new Intent(myIntentAction),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED).getIntentSender()));
diff --git a/hostsidetests/silentupdate/src/com/android/tests/hostside/silentupdate/SilentUpdateHostsideTests.java b/hostsidetests/silentupdate/src/com/android/tests/hostside/silentupdate/SilentUpdateHostsideTests.java
index fb8ad92..35c5a72 100644
--- a/hostsidetests/silentupdate/src/com/android/tests/hostside/silentupdate/SilentUpdateHostsideTests.java
+++ b/hostsidetests/silentupdate/src/com/android/tests/hostside/silentupdate/SilentUpdateHostsideTests.java
@@ -135,6 +135,25 @@
"silentInstallRepeatedly_waitForThrottleTime_succeed");
}
+ @Test
+ public void newInstall_withInstallDpcPermission_requiresUserAction() throws Exception {
+ runDeviceTests(
+ TEST_PKG, TEST_CLS, "newInstall_withInstallDpcPermission_requiresUserAction");
+ }
+
+ @Test
+ public void newInstallDpc_withInstallDpcPermission_requiresNoUserAction() throws Exception {
+ runDeviceTests(
+ TEST_PKG, TEST_CLS, "newInstallDpc_withInstallDpcPermission_requiresNoUserAction");
+ }
+
+ @Test
+ public void newInstallDpc_withoutInstallDpcPermission_requiresUserAction() throws Exception {
+ runDeviceTests(
+ TEST_PKG, TEST_CLS,
+ "newInstallDpc_withoutInstallDpcPermission_requiresUserAction");
+ }
+
private void waitForPathChange(String packageName, String originalCodePath, long timeout)
throws DeviceNotAvailableException, InterruptedException {
long startTime = System.currentTimeMillis();
diff --git a/hostsidetests/silentupdate/testapp/Android.bp b/hostsidetests/silentupdate/testapp/Android.bp
index d3417e9..277198a 100644
--- a/hostsidetests/silentupdate/testapp/Android.bp
+++ b/hostsidetests/silentupdate/testapp/Android.bp
@@ -27,6 +27,8 @@
"androidx.core_core",
"androidx.test.runner",
"truth-prebuilt",
+ "TestApp",
+ "Nene",
],
sdk_version: "test_current",
test_suites: [
diff --git a/hostsidetests/silentupdate/testapp/src/com/android/tests/silentupdate/SilentUpdateTests.java b/hostsidetests/silentupdate/testapp/src/com/android/tests/silentupdate/SilentUpdateTests.java
index 4d72747..5326d6f 100644
--- a/hostsidetests/silentupdate/testapp/src/com/android/tests/silentupdate/SilentUpdateTests.java
+++ b/hostsidetests/silentupdate/testapp/src/com/android/tests/silentupdate/SilentUpdateTests.java
@@ -16,6 +16,7 @@
package com.android.tests.silentupdate;
+import static android.Manifest.permission.INSTALL_DPC_PACKAGES;
import static android.app.PendingIntent.FLAG_MUTABLE;
import static android.content.Context.MODE_PRIVATE;
import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED;
@@ -40,6 +41,11 @@
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.permissions.PermissionContext;
+import com.android.bedstead.testapp.TestApp;
+import com.android.bedstead.testapp.TestAppProvider;
+
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
@@ -68,12 +74,19 @@
private static final String INSTALLER_PACKAGE_NAME = "com.android.tests.silentupdate";
static final long SILENT_UPDATE_THROTTLE_TIME_SECOND = 10;
+ private static final TestAppProvider sTestAppProvider = new TestAppProvider();
+ private static final TestApp sDpcApp = sTestAppProvider.query()
+ .whereIsDeviceAdmin().isTrue()
+ .whereTestOnly().isFalse()
+ .get();
+
private static Context getContext() {
return InstrumentationRegistry.getInstrumentation().getContext();
}
@After
public void tearDown() {
+ sDpcApp.uninstall();
resetSilentUpdatesPolicy();
}
@@ -209,6 +222,39 @@
silentInstallResource(CURRENT_APK));
}
+ @Test
+ public void newInstall_withInstallDpcPermission_requiresUserAction() throws Exception {
+ try (PermissionContext p = TestApis.permissions().withPermission(
+ INSTALL_DPC_PACKAGES)) {
+ Assert.assertEquals("Installing a non DPC package with INSTALL_DPC_PACKAGES "
+ + "permission should require user action.",
+ PackageInstaller.STATUS_PENDING_USER_ACTION,
+ silentInstallResource(CURRENT_APK));
+ }
+ }
+
+ @Test
+ public void newInstallDpc_withInstallDpcPermission_requiresNoUserAction() throws Exception {
+ try (PermissionContext p = TestApis.permissions().withPermission(
+ INSTALL_DPC_PACKAGES)) {
+ Assert.assertEquals("Installing a DPC package with INSTALL_DPC_PACKAGES "
+ + "permission should not require user action.",
+ PackageInstaller.STATUS_SUCCESS,
+ install(sDpcApp::apkStream, /* requireUserAction= */ false));
+ }
+ }
+
+ @Test
+ public void newInstallDpc_withoutInstallDpcPermission_requiresUserAction() throws Exception {
+ try (PermissionContext p = TestApis.permissions().withoutPermission(
+ INSTALL_DPC_PACKAGES)) {
+ Assert.assertEquals("Installing a DPC package without INSTALL_DPC_PACKAGES "
+ + "permission should require user action.",
+ PackageInstaller.STATUS_PENDING_USER_ACTION,
+ install(sDpcApp::apkStream, /* requireUserAction= */ false));
+ }
+ }
+
private int silentInstallResource(String resourceName) throws Exception {
return install(resourceSupplier(resourceName), false);
}
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
index 712c563..7704070 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
@@ -16,8 +16,8 @@
package com.android.tests.stagedinstall;
+import static com.android.cts.install.lib.PackageInstallerSessionInfoSubject.assertThat;
import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
-import static com.android.tests.stagedinstall.PackageInstallerSessionInfoSubject.assertThat;
import static com.google.common.truth.Truth.assertThat;
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/PackageInstallerSessionInfoSubject.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/PackageInstallerSessionInfoSubject.java
deleted file mode 100644
index fa0504d..0000000
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/PackageInstallerSessionInfoSubject.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.tests.stagedinstall;
-
-import android.content.pm.PackageInstaller;
-
-import com.google.common.truth.FailureMetadata;
-import com.google.common.truth.Subject;
-import com.google.common.truth.Truth;
-
-import javax.annotation.Nullable;
-
-final class PackageInstallerSessionInfoSubject extends Subject {
- private final PackageInstaller.SessionInfo mActual;
-
- private PackageInstallerSessionInfoSubject(FailureMetadata failureMetadata,
- @Nullable PackageInstaller.SessionInfo subject) {
- super(failureMetadata, subject);
- mActual = subject;
- }
-
- private static Subject.Factory<PackageInstallerSessionInfoSubject,
- PackageInstaller.SessionInfo> sessions() {
- return new Subject.Factory<PackageInstallerSessionInfoSubject,
- PackageInstaller.SessionInfo>() {
- @Override
- public PackageInstallerSessionInfoSubject createSubject(FailureMetadata failureMetadata,
- PackageInstaller.SessionInfo session) {
- return new PackageInstallerSessionInfoSubject(failureMetadata, session);
- }
- };
- }
-
- static PackageInstallerSessionInfoSubject assertThat(
- PackageInstaller.SessionInfo session) {
- return Truth.assertAbout(sessions()).that(session);
- }
-
- public void isStagedSessionReady() {
- check(failureMessage("in state READY")).that(mActual.isStagedSessionReady()).isTrue();
- }
-
- public void isStagedSessionApplied() {
- check(failureMessage("in state APPLIED")).that(mActual.isStagedSessionApplied()).isTrue();
- }
-
- public void isStagedSessionFailed() {
- check(failureMessage("in state FAILED")).that(mActual.isStagedSessionFailed()).isTrue();
- }
-
- private String failureMessage(String suffix) {
- return String.format("Not true that session %s is %s", subjectAsString(), suffix);
- }
-
- private String subjectAsString() {
- return "{" + "appPackageName = " + mActual.getAppPackageName() + "; "
- + "sessionId = " + mActual.getSessionId() + "; "
- + "isStagedSessionReady = " + mActual.isStagedSessionReady() + "; "
- + "isStagedSessionApplied = " + mActual.isStagedSessionApplied() + "; "
- + "isStagedSessionFailed = " + mActual.isStagedSessionFailed() + "; "
- + "stagedSessionErrorMessage = " + mActual.getStagedSessionErrorMessage() + "}";
- }
-}
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
index e7d419b..6d37814 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
@@ -19,11 +19,11 @@
import static com.android.cts.install.lib.InstallUtils.assertStatusFailure;
import static com.android.cts.install.lib.InstallUtils.assertStatusSuccess;
import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
+import static com.android.cts.install.lib.PackageInstallerSessionInfoSubject.assertThat;
import static com.android.cts.shim.lib.ShimPackage.DIFFERENT_APEX_PACKAGE_NAME;
import static com.android.cts.shim.lib.ShimPackage.NOT_PRE_INSTALL_APEX_PACKAGE_NAME;
import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
import static com.android.cts.shim.lib.ShimPackage.SHIM_PACKAGE_NAME;
-import static com.android.tests.stagedinstall.PackageInstallerSessionInfoSubject.assertThat;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -75,7 +75,9 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@@ -549,7 +551,7 @@
int sessionId = stageSingleApk(Apex2DifferentPackageName).assertSuccessful().getSessionId();
PackageInstaller.SessionInfo sessionInfo = waitForBroadcast(sessionId);
assertThat(sessionInfo.getStagedSessionErrorMessage()).contains(
- "It is forbidden to install new APEX packages.");
+ "Attempting to install new APEX package");
}
@Test
@@ -749,7 +751,6 @@
public void testStagedApkSessionCallbacks() throws Exception {
List<Integer> created = new ArrayList<Integer>();
- List<Integer> finished = new ArrayList<Integer>();
HandlerThread handlerThread = new HandlerThread(
"StagedApkSessionCallbacksTestHandlerThread");
@@ -768,13 +769,7 @@
@Override public void onBadgingChanged(int sessionId) { }
@Override public void onActiveChanged(int sessionId, boolean active) { }
@Override public void onProgressChanged(int sessionId, float progress) { }
-
- @Override
- public void onFinished(int sessionId, boolean success) {
- synchronized (finished) {
- finished.add(sessionId);
- }
- }
+ @Override public void onFinished(int sessionId, boolean success) { }
};
Context context = InstrumentationRegistry.getInstrumentation().getContext();
@@ -795,9 +790,6 @@
synchronized (created) {
assertThat(created).containsExactly(sessionId);
}
- synchronized (finished) {
- assertThat(finished).containsExactly(sessionId);
- }
packageInstaller.abandonSession(sessionId);
}
@@ -1348,7 +1340,7 @@
InstallUtils.commitExpectingFailure(
AssertionError.class,
- "Downgrade of APEX package com.android.apex.cts.shim is not allowed",
+ "INSTALL_FAILED_VERSION_DOWNGRADE",
Install.single(Apex2Rebootless));
assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
}
@@ -1370,7 +1362,7 @@
InstallUtils.commitExpectingFailure(
AssertionError.class,
- "It is forbidden to install new APEX packages",
+ "Attempting to install new APEX package",
Install.single(Apex2DifferentPackageName));
assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
}
@@ -1415,7 +1407,7 @@
InstallUtils.commitExpectingFailure(
AssertionError.class,
- "Failed collecting certificates for",
+ "Failed to collect certificates from",
Install.single(Apex2NoApkSignature));
assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
}
@@ -1431,6 +1423,23 @@
assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
}
+ @Test
+ public void testGetInactiveApexFactoryPackagesAfterApexInstall_containsNoDuplicates()
+ throws Exception {
+ int flags = (PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY
+ | PackageManager.MATCH_UNINSTALLED_PACKAGES);
+ List<PackageInfo> packageInfos =
+ InstrumentationRegistry.getInstrumentation().getContext().getPackageManager()
+ .getInstalledPackages(flags);
+ Set<String> foundPackages = new HashSet<>();
+ for (PackageInfo pi : packageInfos) {
+ if (foundPackages.contains(pi.packageName)) {
+ throw new AssertionError(pi.packageName + " is listed at least twice.");
+ }
+ foundPackages.add(pi.packageName);
+ }
+ }
+
// It becomes harder to maintain this variety of install-related helper methods.
// TODO(ioffe): refactor install-related helper methods into a separate utility.
private static int createStagedSession() throws Exception {
diff --git a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
index 8aed91b..e98eeda 100644
--- a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
@@ -824,6 +824,7 @@
}
@Test
+ @LargeTest
public void testRebootlessUpdate_fromV2ToV3_sameBoot() throws Exception {
assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
@@ -910,6 +911,16 @@
runPhase("testRebootlessUpdate_targetsOlderSdk_fails");
}
+ @Test
+ @LargeTest
+ public void testGetInactiveApexFactoryPackagesAfterApexInstall_containsNoDuplicates()
+ throws Exception {
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+
+ installV2Apex();
+ runPhase("testGetInactiveApexFactoryPackagesAfterApexInstall_containsNoDuplicates");
+ }
+
private List<ApexInfo> readApexInfoList() throws Exception {
File file = getDevice().pullFile("/apex/apex-info-list.xml");
try (FileInputStream stream = new FileInputStream(file)) {
diff --git a/hostsidetests/statsdatom/Android.bp b/hostsidetests/statsdatom/Android.bp
index 6f2da5f..1f793b3 100644
--- a/hostsidetests/statsdatom/Android.bp
+++ b/hostsidetests/statsdatom/Android.bp
@@ -36,6 +36,7 @@
"src/**/gnss/*.java",
"src/**/jobscheduler/*.java",
"src/**/integrity/*.java",
+ "src/**/media/*.java",
"src/**/memory/*.java",
"src/**/net/*.java",
"src/**/notification/*.java",
@@ -72,7 +73,11 @@
data: [
":CtsStatsdAtomApp",
":CtsStatsdApp", //TODO(b/163546661): Remove once migration to new lib is complete.
- ]
+ ":CtsAppExitTestCases",
+ ":CtsExternalServiceService",
+ ":CtsSimpleApp",
+ ],
+ per_testcase_directory: true,
}
java_library_host {
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
index ef24a7a..9599f76 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
@@ -728,13 +728,36 @@
builder.setOverrideDeadline(0);
JobInfo job = builder.build();
- long startTime = System.currentTimeMillis();
CountDownLatch latch = StatsdJobService.resetCountDownLatch();
js.schedule(job);
waitForReceiver(context, 5_000, latch, null);
}
@Test
+ public void testScheduledJobPriority() throws Exception {
+ final ComponentName name =
+ new ComponentName(MY_PACKAGE_NAME, StatsdJobService.class.getName());
+
+ Context context = InstrumentationRegistry.getContext();
+ JobScheduler js = context.getSystemService(JobScheduler.class);
+ assertWithMessage("JobScheduler service not available").that(js).isNotNull();
+
+ final int[] priorities = {
+ JobInfo.PRIORITY_HIGH, JobInfo.PRIORITY_DEFAULT,
+ JobInfo.PRIORITY_LOW, JobInfo.PRIORITY_MIN};
+ for (int priority : priorities) {
+ JobInfo job = new JobInfo.Builder(priority, name)
+ .setOverrideDeadline(0)
+ .setPriority(priority)
+ .build();
+
+ CountDownLatch latch = StatsdJobService.resetCountDownLatch();
+ js.schedule(job);
+ waitForReceiver(context, 5_000, latch, null);
+ }
+ }
+
+ @Test
public void testVibratorState() {
Context context = InstrumentationRegistry.getContext();
Vibrator vib = context.getSystemService(Vibrator.class);
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/appexit/AppExitHostTest.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/appexit/AppExitHostTest.java
index e8c42d1..9abd8c2 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/appexit/AppExitHostTest.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/appexit/AppExitHostTest.java
@@ -33,15 +33,23 @@
import com.android.os.AtomsProto;
import com.android.os.StatsLog;
import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.List;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-public class AppExitHostTest extends DeviceTestCase implements IBuildReceiver {
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class AppExitHostTest extends BaseHostJUnit4Test implements IBuildReceiver {
private static final String TEST_PKG = "android.app.cts.appexit";
private static final String HELPER_PKG1 = "android.externalservice.service";
private static final String HELPER_PKG2 = "com.android.cts.launcherapps.simpleapp";
@@ -55,9 +63,8 @@
private IBuildInfo mCtsBuild;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
assertThat(mCtsBuild).isNotNull();
ConfigUtils.removeConfig(getDevice());
ReportUtils.clearReports(getDevice());
@@ -71,8 +78,8 @@
getDevice().executeShellCommand("pm grant " + TEST_PKG + " " + PERM_READ_LOGS);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
ConfigUtils.removeConfig(getDevice());
ReportUtils.clearReports(getDevice());
getDevice().executeShellCommand("pm revoke " + TEST_PKG + " " + PERM_PACKAGE_USAGE_STATS);
@@ -80,7 +87,6 @@
DeviceUtils.uninstallTestApp(getDevice(), TEST_PKG);
DeviceUtils.uninstallTestApp(getDevice(), HELPER_PKG1);
DeviceUtils.uninstallTestApp(getDevice(), HELPER_PKG2);
- super.tearDown();
}
@Override
@@ -88,6 +94,7 @@
mCtsBuild = buildInfo;
}
+ @Test
public void testLogStatsdPermChanged() throws Exception {
final String helperPackage = HELPER_PKG2;
final int expectedUid = getAppUid(helperPackage);
@@ -101,6 +108,7 @@
});
}
+ @Test
public void testLogStatsdOther() throws Exception {
final String helperPackage = HELPER_PKG1;
final int expectedUid = getAppUid(TEST_PKG);
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
index e550db6..0ccc450 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
@@ -52,8 +52,7 @@
final int APP_OP_RECORD_AUDIO = 27;
final int APP_OP_RECORD_AUDIO_HOTWORD = 102;
- // Temporarily commented out until the Trusted Hotword requirement is enforced again.
-// TRANSFORMED_FROM_OP.put(APP_OP_RECORD_AUDIO, APP_OP_RECORD_AUDIO_HOTWORD);
+ TRANSFORMED_FROM_OP.put(APP_OP_RECORD_AUDIO, APP_OP_RECORD_AUDIO_HOTWORD);
}
private IBuildInfo mCtsBuild;
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/bluetooth/BluetoothStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/bluetooth/BluetoothStatsTests.java
index ca75fc3..c84d60e 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/bluetooth/BluetoothStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/bluetooth/BluetoothStatsTests.java
@@ -82,7 +82,7 @@
Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
- AtomTestUtils.assertStatesOccurred(stateSet, data, expectedWait,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, expectedWait,
atom -> atom.getBleScanStateChanged().getState().getNumber());
}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/jobscheduler/JobSchedulerStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/jobscheduler/JobSchedulerStatsTests.java
index d282ffb..efc1ae5 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/jobscheduler/JobSchedulerStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/jobscheduler/JobSchedulerStatsTests.java
@@ -29,12 +29,18 @@
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IBuildReceiver;
-import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class JobSchedulerStatsTests extends DeviceTestCase implements IBuildReceiver {
+ private static final Set<Integer> STATE_SCHEDULE = new HashSet<>(
+ List.of(AtomsProto.ScheduledJobStateChanged.State.SCHEDULED_VALUE));
+ private static final Set<Integer> STATE_START = new HashSet<>(
+ List.of(AtomsProto.ScheduledJobStateChanged.State.STARTED_VALUE));
+ private static final Set<Integer> STATE_FINISH = new HashSet<>(
+ List.of(AtomsProto.ScheduledJobStateChanged.State.FINISHED_VALUE));
+
private static final String JOB_NAME =
"com.android.server.cts.device.statsdatom/.StatsdJobService";
@@ -65,15 +71,9 @@
public void testScheduledJobState() throws Exception {
final int atomTag = AtomsProto.Atom.SCHEDULED_JOB_STATE_CHANGED_FIELD_NUMBER;
- Set<Integer> jobSchedule = new HashSet<>(
- Arrays.asList(AtomsProto.ScheduledJobStateChanged.State.SCHEDULED_VALUE));
- Set<Integer> jobOn = new HashSet<>(
- Arrays.asList(AtomsProto.ScheduledJobStateChanged.State.STARTED_VALUE));
- Set<Integer> jobOff = new HashSet<>(
- Arrays.asList(AtomsProto.ScheduledJobStateChanged.State.FINISHED_VALUE));
// Add state sets to the list in order.
- List<Set<Integer>> stateSet = Arrays.asList(jobSchedule, jobOn, jobOff);
+ List<Set<Integer>> stateSet = List.of(STATE_SCHEDULE, STATE_START, STATE_FINISH);
ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
atomTag, /*useUidAttributionChain=*/true);
@@ -83,7 +83,7 @@
// Sorted list of events in order in which they occurred.
List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
- AtomTestUtils.assertStatesOccurred(stateSet, data, 0,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, 0,
atom -> atom.getScheduledJobStateChanged().getState().getNumber());
for (StatsLog.EventMetricData e : data) {
@@ -91,4 +91,35 @@
.isEqualTo(JOB_NAME);
}
}
+
+ public void testScheduledJobStatePriority() throws Exception {
+ final int atomTag = AtomsProto.Atom.SCHEDULED_JOB_STATE_CHANGED_FIELD_NUMBER;
+
+ // Add state sets to the list in order.
+ List<Set<Integer>> stateSet = List.of(
+ STATE_SCHEDULE, STATE_START, STATE_FINISH,
+ STATE_SCHEDULE, STATE_START, STATE_FINISH,
+ STATE_SCHEDULE, STATE_START, STATE_FINISH,
+ STATE_SCHEDULE, STATE_START, STATE_FINISH
+ );
+
+ ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ atomTag, /*useUidAttributionChain=*/true);
+ DeviceUtils.allowImmediateSyncs(getDevice());
+ DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests",
+ "testScheduledJobPriority");
+
+ // Sorted list of events in order in which they occurred.
+ List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+ AtomTestUtils.assertStatesOccurred(stateSet, data,
+ atom -> atom.getScheduledJobStateChanged().getState().getNumber());
+
+ for (StatsLog.EventMetricData e : data) {
+ assertThat(e.getAtom().getScheduledJobStateChanged().getJobName())
+ .isEqualTo(JOB_NAME);
+ assertThat(e.getAtom().getScheduledJobStateChanged().getRequestedPriority())
+ .isEqualTo(e.getAtom().getScheduledJobStateChanged().getJobId());
+ }
+ }
}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/AtomTestUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/AtomTestUtils.java
index dbb6699..3b2aa8c 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/AtomTestUtils.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/AtomTestUtils.java
@@ -24,6 +24,7 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil;
+import com.android.utils.SparseIntArray;
import com.google.common.collect.Range;
@@ -56,6 +57,37 @@
}
/**
+ * Asserts that each set of states in {@code stateSets} occurs in {@code data} without assuming
+ * the order of occurrence.
+ *
+ * @param stateSets A list of set of states, where each set represents an equivalent
+ * state of the device for the purpose of CTS.
+ * @param data list of EventMetricData from statsd, produced by
+ * getReportMetricListData()
+ * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
+ */
+ public static void assertStatesOccurred(List<Set<Integer>> stateSets,
+ List<StatsLog.EventMetricData> data,
+ Function<AtomsProto.Atom, Integer> getStateFromAtom) {
+ // Sometimes, there are more events than there are states.
+ // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
+ assertWithMessage("Number of result states").that(data.size()).isAtLeast(stateSets.size());
+ final SparseIntArray dataStateCount = new SparseIntArray();
+ for (StatsLog.EventMetricData emd : data) {
+ final int state = getStateFromAtom.apply(emd.getAtom());
+ dataStateCount.put(state, dataStateCount.get(state, 0) + 1);
+ }
+ for (Set<Integer> states : stateSets) {
+ for (int state : states) {
+ final int count = dataStateCount.get(state);
+ assertWithMessage("Remaining count of result state (%s)", state)
+ .that(count).isGreaterThan(0);
+ dataStateCount.put(state, count - 1);
+ }
+ }
+ }
+
+ /**
* Asserts that each set of states in stateSets occurs at least once in data.
* Asserts that the states in data occur in the same order as the sets in stateSets.
*
@@ -69,12 +101,12 @@
* assertion.
* @param getStateFromAtom expression that takes in an Atom and returns the state it contains
*/
- public static void assertStatesOccurred(List<Set<Integer>> stateSets,
+ public static void assertStatesOccurredInOrder(List<Set<Integer>> stateSets,
List<StatsLog.EventMetricData> data,
int wait, Function<AtomsProto.Atom, Integer> getStateFromAtom) {
// Sometimes, there are more events than there are states.
// Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
- assertWithMessage("Too few states found").that(data.size()).isAtLeast(stateSets.size());
+ assertWithMessage("Number of result states").that(data.size()).isAtLeast(stateSets.size());
int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
AtomsProto.Atom atom = data.get(dataIndex).getAtom();
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/media/MediaCapabilitiesTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/media/MediaCapabilitiesTests.java
new file mode 100644
index 0000000..313afc5
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/media/MediaCapabilitiesTests.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsdatom.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+import android.stats.mediametrics.Mediametrics;
+
+import com.android.os.AtomsProto;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.List;
+
+public class MediaCapabilitiesTests extends DeviceTestCase implements IBuildReceiver {
+ private static final String FEATURE_TV = "android.hardware.type.television";
+ private IBuildInfo mCtsBuild;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ assertThat(mCtsBuild).isNotNull();
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ DeviceUtils.uninstallStatsdTestApp(getDevice());
+ super.tearDown();
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = buildInfo;
+ }
+
+ public void testSurroundSoundCapabilities() throws Exception {
+ // Run this test only on TVs
+ if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TV)) return;
+
+ ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ AtomsProto.Atom.MEDIA_CAPABILITIES_FIELD_NUMBER);
+
+ // Store the original value of settings
+ String isDtsEnabled = getDevice().executeShellCommand(
+ "cmd audio get-is-surround-format-enabled 7").split(":")[1].trim();
+ String isDolbyTrueHdEnabled = getDevice().executeShellCommand(
+ "cmd audio get-is-surround-format-enabled 14").split(":")[1].trim();
+ String encodedSurroundMode = getDevice().executeShellCommand(
+ "cmd audio get-encoded-surround-mode").split(":")[1].trim();
+
+ // Setting the values of audio setting via shell commands
+ getDevice().executeShellCommand(
+ "cmd audio set-surround-format-enabled 7 true");
+ getDevice().executeShellCommand(
+ "cmd audio set-surround-format-enabled 14 false");
+ getDevice().executeShellCommand("cmd audio set-encoded-surround-mode 2");
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+ // Trigger atom pull.
+ AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+ // The list of atoms will be empty if the atom is not supported.
+ List<AtomsProto.Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+ for (AtomsProto.Atom atom : atoms) {
+ assertThat(atom.getMediaCapabilities().getSurroundEncodings().getAudioEncodingsCount())
+ .isAtLeast(1);
+ assertEquals(Mediametrics.EncodedSurroundOutputMode.ENCODED_SURROUND_OUTPUT_NEVER,
+ atom.getMediaCapabilities().getSurroundOutputMode());
+ assertThat(Mediametrics.AudioEncoding.ENCODING_DTS).isIn(
+ atom.getMediaCapabilities()
+ .getUserEnabledSurroundEncodings().getAudioEncodingsList());
+ assertThat(Mediametrics.AudioEncoding.ENCODING_DOLBY_TRUEHD).isNotIn(
+ atom.getMediaCapabilities()
+ .getUserEnabledSurroundEncodings().getAudioEncodingsList());
+ }
+
+ // Restore the original value of settings
+ getDevice().executeShellCommand(
+ "cmd audio set-surround-format-enabled 7 " + isDtsEnabled);
+ getDevice().executeShellCommand(
+ "cmd audio set-surround-format-enabled 14 " + isDolbyTrueHdEnabled);
+ getDevice().executeShellCommand(
+ "cmd audio set-encoded-surround-mode " + encodedSurroundMode);
+ }
+
+ public void testDisplayCapabilities() throws Exception {
+ // Run this test only on TVs
+ if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TV)) return;
+
+ ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ AtomsProto.Atom.MEDIA_CAPABILITIES_FIELD_NUMBER);
+
+ // Store the original value of settings
+ String userPrefDisplayMode = getDevice().executeShellCommand(
+ "cmd display get-user-preferred-display-mode").split(":")[1].trim();
+ String matchContentFrameRatePref = getDevice().executeShellCommand(
+ "cmd display get-match-content-frame-rate-pref").split(":")[1].trim();
+ String userDisabledHdrTypes = getDevice().executeShellCommand(
+ "cmd display get-user-disabled-hdr-types").split(":")[1].trim();
+ userDisabledHdrTypes.replaceAll(", ", " ");
+
+
+ // Setting the values of display setting via shell commands
+ getDevice().executeShellCommand(
+ "cmd display set-user-preferred-display-mode 720 1020 60.0f");
+ getDevice().executeShellCommand("cmd display set-match-content-frame-rate-pref 2");
+ getDevice().executeShellCommand("cmd display set-user-disabled-hdr-types 0 1");
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+ // Trigger atom pull.
+ AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+ // The list of atoms will be empty if the atom is not supported.
+ List<AtomsProto.Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+ for (AtomsProto.Atom atom : atoms) {
+ assertThat(atom.getMediaCapabilities().getSinkDisplayModes().getDisplayModesCount())
+ .isAtLeast(1);
+ assertEquals(720, atom.getMediaCapabilities().getUserPreferredResolutionHeight());
+ assertEquals(1020, atom.getMediaCapabilities().getUserPreferredResolutionWidth());
+ assertEquals(60.0f, atom.getMediaCapabilities().getUserPreferredRefreshRate());
+ assertEquals(Mediametrics.MatchContentFrameRatePreference
+ .MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY,
+ atom.getMediaCapabilities().getMatchContentRefreshRatePreference());
+ assertThat(
+ atom.getMediaCapabilities().getUserDisabledHdrFormats().getHdrFormats(0))
+ .isAnyOf(
+ Mediametrics.HdrFormat.HDR_TYPE_DOLBY_VISION,
+ Mediametrics.HdrFormat.HDR_TYPE_HDR10);
+ assertThat(
+ atom.getMediaCapabilities().getUserDisabledHdrFormats().getHdrFormats(1))
+ .isAnyOf(
+ Mediametrics.HdrFormat.HDR_TYPE_DOLBY_VISION,
+ Mediametrics.HdrFormat.HDR_TYPE_HDR10);
+ }
+
+ // Restore the original value of settings
+ if (userPrefDisplayMode.equals("null")) {
+ getDevice().executeShellCommand(
+ "cmd display clear-user-preferred-display-mode");
+ } else {
+ getDevice().executeShellCommand(
+ "cmd display set-user-preferred-display-mode " + userPrefDisplayMode);
+ }
+ getDevice().executeShellCommand("cmd display set-match-content-frame-rate-pref "
+ + matchContentFrameRatePref);
+ getDevice().executeShellCommand("cmd display set-user-disabled-hdr-types "
+ + userDisabledHdrTypes);
+ }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/media/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/media/OWNERS
new file mode 100644
index 0000000..5af70bb
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/media/OWNERS
@@ -0,0 +1,2 @@
+kritidang@google.com
+blindahl@google.com
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/HostAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/HostAtomTests.java
index ee1d0e0..8b6f991 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/HostAtomTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/HostAtomTests.java
@@ -28,16 +28,12 @@
import android.platform.test.annotations.RestrictedBuildTest;
import android.server.DeviceIdleModeEnum;
import android.view.DisplayStateEnum;
-import android.telephony.NetworkTypeEnum;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
import com.android.os.AtomsProto.AppBreadcrumbReported;
import com.android.os.AtomsProto.Atom;
import com.android.os.AtomsProto.BatterySaverModeStateChanged;
import com.android.os.AtomsProto.BuildInformation;
import com.android.os.AtomsProto.ConnectivityStateChanged;
-import com.android.os.AtomsProto.SimSlotState;
-import com.android.os.AtomsProto.SupportedRadioAccessFamily;
import com.android.os.StatsLog.ConfigMetricsReportList;
import com.android.os.StatsLog.EventMetricData;
import com.android.tradefed.build.IBuildInfo;
@@ -50,15 +46,9 @@
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
-import java.util.Queue;
import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* Statsd atom tests that are done via adb (hostside).
@@ -149,7 +139,7 @@
// Restores AoD to initial state.
setAodState(aodState);
// Assert that the events happened in the expected order.
- AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
atom -> atom.getScreenStateChanged().getState().getNumber());
}
@@ -199,7 +189,7 @@
Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
// Assert that the events happened in the expected order.
- AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
atom -> atom.getChargingStateChanged().getState().getNumber());
}
@@ -255,7 +245,7 @@
Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
// Assert that the events happened in the expected order.
- AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
atom -> atom.getPluggedStateChanged().getState().getNumber());
}
@@ -263,6 +253,7 @@
if (DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)) return;
// Setup, set battery level to full.
setBatteryLevel(100);
+ DeviceUtils.flushBatteryStatsHandlers(getDevice());
Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
final int atomTag = Atom.BATTERY_LEVEL_CHANGED_FIELD_NUMBER;
@@ -282,15 +273,20 @@
// Trigger events in same order.
setBatteryLevel(2);
- Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+ DeviceUtils.flushBatteryStatsHandlers(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
setBatteryLevel(25);
- Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+ DeviceUtils.flushBatteryStatsHandlers(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
setBatteryLevel(50);
- Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+ DeviceUtils.flushBatteryStatsHandlers(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
setBatteryLevel(75);
- Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+ DeviceUtils.flushBatteryStatsHandlers(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
setBatteryLevel(100);
- Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+ DeviceUtils.flushBatteryStatsHandlers(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
// Sorted list of events in order in which they occurred.
List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
@@ -300,7 +296,7 @@
Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
// Assert that the events happened in the expected order.
- AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
atom -> atom.getBatteryLevelChanged().getBatteryLevel());
}
@@ -336,7 +332,7 @@
List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
// Assert that the events happened in the expected order.
- AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
atom -> atom.getDeviceIdleModeStateChanged().getState().getNumber());
}
@@ -370,7 +366,7 @@
List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
// Assert that the events happened in the expected order.
- AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
atom -> atom.getBatterySaverModeStateChanged().getState().getNumber());
}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateAtomTests.java
index 11353ce..2608ca8 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateAtomTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateAtomTests.java
@@ -158,7 +158,7 @@
List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
AtomTestUtils.popUntilFind(data, onStates,
PROC_STATE_FUNCTION); // clear out initial proc states.
- AtomTestUtils.assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, waitTime, PROC_STATE_FUNCTION);
}
public void testForeground() throws Exception {
@@ -177,7 +177,7 @@
List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
AtomTestUtils.popUntilFind(data, onStates,
PROC_STATE_FUNCTION); // clear out initial proc states.
- AtomTestUtils.assertStatesOccurred(stateSet, data, 0, PROC_STATE_FUNCTION);
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, 0, PROC_STATE_FUNCTION);
}
public void testBackground() throws Exception {
@@ -195,7 +195,7 @@
List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
AtomTestUtils.popUntilFind(data, onStates,
PROC_STATE_FUNCTION); // clear out initial proc states.
- AtomTestUtils.assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, waitTime, PROC_STATE_FUNCTION);
}
public void testTop() throws Exception {
@@ -214,7 +214,7 @@
List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
AtomTestUtils.popUntilFind(data, onStates,
PROC_STATE_FUNCTION); // clear out initial proc states.
- AtomTestUtils.assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, waitTime, PROC_STATE_FUNCTION);
}
public void testTopSleeping() throws Exception {
@@ -244,7 +244,7 @@
// reset screen back on
DeviceUtils.turnScreenOn(getDevice());
// Don't check the wait time, since it's up to the system how long top sleeping persists.
- AtomTestUtils.assertStatesOccurred(stateSet, data, 0, PROC_STATE_FUNCTION);
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, 0, PROC_STATE_FUNCTION);
}
public void testCached() throws Exception {
@@ -276,7 +276,7 @@
// Now clear out the bg state from step #2 (since we are interested in the cache after it).
AtomTestUtils.popUntilFind(data, onStates, PROC_STATE_FUNCTION);
// The result is that data should start at step #3, definitively in a cached state.
- AtomTestUtils.assertStatesOccurred(stateSet, data, 1_000, PROC_STATE_FUNCTION);
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, 1_000, PROC_STATE_FUNCTION);
}
public void testValidityOfStates() throws Exception {
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
index 0a00c67..0cf7fce 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
@@ -57,8 +57,8 @@
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.util.Pair;
-import java.util.Arrays;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -308,11 +308,15 @@
List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
// Assert that the events happened in the expected order.
- AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
atom -> atom.getCameraStateChanged().getState().getNumber());
}
public void testDeviceCalculatedPowerUse() throws Exception {
+ if (DeviceUtils.hasFeature(getDevice(), FEATURE_TV)) {
+ // Skip TVs because they do not have batteries.
+ return;
+ }
if (!DeviceUtils.hasFeature(getDevice(), FEATURE_LEANBACK_ONLY)) return;
ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
@@ -394,7 +398,7 @@
List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
// Assert that the events happened in the expected order.
- AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
atom -> atom.getFlashlightStateChanged().getState().getNumber());
}
@@ -419,7 +423,7 @@
List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
// Assert that the events happened in the expected order.
- AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
atom -> atom.getForegroundServiceStateChanged().getState().getNumber());
}
@@ -540,7 +544,7 @@
List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
// Assert that the events happened in the expected order.
- AtomTestUtils.assertStatesOccurred(stateSet, data, videoDuration,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, videoDuration,
atom -> atom.getMediaCodecStateChanged().getState().getNumber());
}
@@ -568,7 +572,7 @@
// Assert that the events happened in the expected order.
// The overlay box should appear about 2sec after the app start
- AtomTestUtils.assertStatesOccurred(stateSet, data, 0,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, 0,
atom -> atom.getOverlayStateChanged().getState().getNumber());
}
@@ -659,7 +663,7 @@
AtomTestUtils.popUntilFindFromEnd(data, screen140,
atom -> atom.getScreenBrightnessChanged().getLevel());
// Assert that the events happened in the expected order.
- AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
atom -> atom.getScreenBrightnessChanged().getLevel());
}
@@ -680,7 +684,7 @@
List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
// Assert that the events happened in the expected order.
- AtomTestUtils.assertStatesOccurred(stateSet, data,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data,
/* wait = */ 0 /* don't verify time differences between state changes */,
atom -> atom.getSyncStateChanged().getState().getNumber());
}
@@ -708,7 +712,7 @@
// Sorted list of events in order in which they occurred.
List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
- AtomTestUtils.assertStatesOccurred(stateSet, data, 300,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, 300,
atom -> atom.getVibratorStateChanged().getState().getNumber());
}
@@ -735,7 +739,7 @@
List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
// Assert that the events happened in the expected order.
- AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
atom -> atom.getWakelockStateChanged().getState().getNumber());
for (EventMetricData event : data) {
@@ -877,7 +881,7 @@
atom -> atom.getAppUsageEventOccurred().getEventType().getNumber();
// clear out initial appusage states
AtomTestUtils.popUntilFind(data, onStates, appUsageStateFunction);
- AtomTestUtils.assertStatesOccurred(stateSet, data, 0, appUsageStateFunction);
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, 0, appUsageStateFunction);
}
/*
public void testAppForceStopUsageEvent() throws Exception {
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java
index 8a00db4..87c29c9 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java
@@ -263,6 +263,21 @@
assertThat(data).isNotEmpty();
}
+ public void testPerSimStatus() throws Exception {
+ if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ AtomsProto.Atom.PER_SIM_STATUS_FIELD_NUMBER);
+
+ AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+ List<AtomsProto.Atom> data = ReportUtils.getGaugeMetricAtoms(getDevice());
+ assertThat(data).hasSize(getActiveSimSlotCount());
+ }
+
private boolean hasGsmPhone() throws Exception {
// Not using log entries or ServiceState in the dump since they may or may not be present,
// which can make the test flaky
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/WifiStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/WifiStatsTests.java
index c4bec178..cb3df79 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/WifiStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/WifiStatsTests.java
@@ -91,7 +91,7 @@
List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
// Assert that the events happened in the expected order.
- AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
atom -> atom.getWifiLockStateChanged().getState().getNumber());
for (StatsLog.EventMetricData event : data) {
@@ -120,7 +120,7 @@
List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
// Assert that the events happened in the expected order.
- AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
atom -> atom.getWifiLockStateChanged().getState().getNumber());
for (StatsLog.EventMetricData event : data) {
@@ -152,7 +152,7 @@
List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
// Assert that the events happened in the expected order.
- AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
atom -> atom.getWifiMulticastLockStateChanged().getState().getNumber());
for (StatsLog.EventMetricData event : data) {
diff --git a/hostsidetests/systemui/Android.bp b/hostsidetests/systemui/Android.bp
index 6de52cd..6fd9c8c 100644
--- a/hostsidetests/systemui/Android.bp
+++ b/hostsidetests/systemui/Android.bp
@@ -29,6 +29,7 @@
static_libs: [
"cts-statsd-atom-host-test-utils",
+ "platformprotos",
],
// Tag this module as a cts test artifact
test_suites: [
diff --git a/hostsidetests/systemui/OWNERS b/hostsidetests/systemui/OWNERS
index 7e823ba..a48e7e3 100644
--- a/hostsidetests/systemui/OWNERS
+++ b/hostsidetests/systemui/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 136515
-kozynski@google.com
-dsandler@android.com
-ashaikh@google.com
+# Bug component: 78010
+per-file *TileService* = file:/tests/quicksettings/OWNERS
+per-file *Notification* = juliacr@google.com
+dsandler@android.com #{LAST_RESORT_SUGGESTION}
diff --git a/hostsidetests/testharness/app/src/android/testharness/app/TestHarnessModeDeviceTest.java b/hostsidetests/testharness/app/src/android/testharness/app/TestHarnessModeDeviceTest.java
index a0cb026..bde3390 100644
--- a/hostsidetests/testharness/app/src/android/testharness/app/TestHarnessModeDeviceTest.java
+++ b/hostsidetests/testharness/app/src/android/testharness/app/TestHarnessModeDeviceTest.java
@@ -30,6 +30,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
/** Device-side tests for Test Harness Mode */
@RunWith(AndroidJUnit4.class)
@@ -51,7 +52,7 @@
@Test
public void dirtyDevice() {
TestHarnessActivity activity = mActivityRule.getActivity();
- PollingCheck.waitFor(activity::hasWindowFocus);
+ WindowUtil.waitForFocus(activity);
activity.dirtyDevice();
}
@@ -59,7 +60,7 @@
@Test
public void testDeviceIsClean() {
TestHarnessActivity activity = mActivityRule.getActivity();
- PollingCheck.waitFor(activity::hasWindowFocus);
+ WindowUtil.waitForFocus(activity);
Assert.assertTrue(activity.isDeviceClean());
}
diff --git a/hostsidetests/theme/assets/31/450dpi.zip b/hostsidetests/theme/assets/31/450dpi.zip
deleted file mode 100644
index 5ce9a1e..0000000
--- a/hostsidetests/theme/assets/31/450dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/S/450dpi.zip b/hostsidetests/theme/assets/S/450dpi.zip
new file mode 100755
index 0000000..e346378
--- /dev/null
+++ b/hostsidetests/theme/assets/S/450dpi.zip
Binary files differ
diff --git a/hostsidetests/time/Android.bp b/hostsidetests/time/Android.bp
index 72d27e4..79d85d1 100644
--- a/hostsidetests/time/Android.bp
+++ b/hostsidetests/time/Android.bp
@@ -24,6 +24,9 @@
"cts-statsd-atom-host-test-utils",
"host_time_shell_utils",
],
+ data: [
+ ":CtsFakeTimeZoneProvidersApp",
+ ],
test_suites: ["general-tests", "cts"],
test_config: "host/AndroidTest.xml",
}
diff --git a/hostsidetests/time/device/fake_tzps_app/Android.bp b/hostsidetests/time/device/fake_tzps_app/Android.bp
new file mode 100644
index 0000000..c17e49d
--- /dev/null
+++ b/hostsidetests/time/device/fake_tzps_app/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// An app that hosts fake TimeZoneProviderService implementations that can be used by tests to query
+// the fake providers' state and inject test events into the fake providers.
+android_test_helper_app {
+ name: "CtsFakeTimeZoneProvidersApp",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "test_current",
+ static_libs: [
+ "device_time_shell_utils",
+ ],
+ srcs: ["src/**/*.java"],
+}
diff --git a/hostsidetests/time/device/fake_tzps_app/AndroidManifest.xml b/hostsidetests/time/device/fake_tzps_app/AndroidManifest.xml
new file mode 100644
index 0000000..80898ff
--- /dev/null
+++ b/hostsidetests/time/device/fake_tzps_app/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.time.cts.fake_tzps_app">
+
+ <application android:debuggable="true"
+ android:allowBackup="false">
+ <!-- A content provider that can be used to interact with the fake providers. -->
+ <provider android:name=".fixture.FakeTimeZoneProviderFixtureProvider"
+ android:authorities="faketzpsapp"
+ android:multiprocess="false"
+ android:exported="true" />
+
+ <!-- A primary location time zone provider. -->
+ <service android:name=".tzps.FakeLocationTimeZoneProviderService1"
+ android:permission="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.service.timezone.PrimaryLocationTimeZoneProviderService" />
+ </intent-filter>
+ <meta-data android:name="serviceIsMultiuser" android:value="true" />
+ </service>
+
+ <!-- A secondary location time zone provider. -->
+ <service android:name=".tzps.FakeLocationTimeZoneProviderService2"
+ android:permission="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.service.timezone.SecondaryLocationTimeZoneProviderService" />
+ </intent-filter>
+ <meta-data android:name="serviceIsMultiuser" android:value="true" />
+ </service>
+ </application>
+</manifest>
diff --git a/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/fixture/FakeTimeZoneProviderFixtureProvider.java b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/fixture/FakeTimeZoneProviderFixtureProvider.java
new file mode 100644
index 0000000..886768e
--- /dev/null
+++ b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/fixture/FakeTimeZoneProviderFixtureProvider.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.time.cts.fake_tzps_app.fixture;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.android.time.cts.fake_tzps_app.tzps.FakeTimeZoneProviderService;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A {@link ContentProvider} that can interact with the fake {@link
+ * android.service.timezone.TimeZoneProviderService} implementations. This enables test code to
+ * interact with the fakes: device-side code can use the usual content provider APIs, and both
+ * device-side and host-side code can use the "adb shell content" command. For simplicity,
+ * everything is implemented using the "call" verb.
+ */
+public class FakeTimeZoneProviderFixtureProvider extends ContentProvider {
+
+ private static final String METHOD_GET_STATE = "get_state";
+ private static final String CALL_RESULT_KEY_GET_STATE_STATE = "state";
+ private static final String METHOD_REPORT_PERMANENT_FAILURE = "perm_fail";
+ private static final String METHOD_REPORT_UNCERTAIN = "uncertain";
+ private static final String METHOD_REPORT_SUCCESS = "success";
+ private static final String METHOD_PING = "ping";
+
+ /** Suggestion time zone IDs. A single string, comma separated, may be empty. */
+ private static final String CALL_EXTRA_KEY_SUGGESTION_ZONE_IDS = "zone_ids";
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ // No impl
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ // No impl
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ // No impl
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ // No impl
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ // No impl
+ return 0;
+ }
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ Objects.requireNonNull(extras);
+
+ if (METHOD_PING.equals(method)) {
+ // No-op - this method just exists to make sure the content provider exists.
+ return Bundle.EMPTY;
+ }
+
+ String providerId = Objects.requireNonNull(arg);
+ FakeTimeZoneProviderService provider = FakeTimeZoneProviderRegistry.getInstance()
+ .getFakeTimeZoneProviderService(providerId);
+ Objects.requireNonNull(provider, "arg=" + providerId + ", provider not found");
+
+ Bundle result = new Bundle();
+ switch (method) {
+ case METHOD_GET_STATE: {
+ result.putInt(CALL_RESULT_KEY_GET_STATE_STATE, provider.getState());
+ break;
+ }
+ case METHOD_REPORT_PERMANENT_FAILURE: {
+ provider.fakeReportPermanentFailure();
+ break;
+ }
+ case METHOD_REPORT_UNCERTAIN: {
+ provider.fakeReportUncertain();
+ break;
+ }
+ case METHOD_REPORT_SUCCESS: {
+ String zoneIdsString = extras.getString(CALL_EXTRA_KEY_SUGGESTION_ZONE_IDS);
+ List<String> zoneIds = Arrays.asList(zoneIdsString.split(","));
+ provider.fakeReportSuggestion(zoneIds);
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("method=" + method + " not known");
+ }
+ }
+ return result;
+ }
+}
diff --git a/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/fixture/FakeTimeZoneProviderRegistry.java b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/fixture/FakeTimeZoneProviderRegistry.java
new file mode 100644
index 0000000..420efda
--- /dev/null
+++ b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/fixture/FakeTimeZoneProviderRegistry.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.time.cts.fake_tzps_app.fixture;
+
+import com.android.time.cts.fake_tzps_app.tzps.FakeTimeZoneProviderService;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A singleton registry of fake {@link android.service.timezone.TimeZoneProviderService} instances.
+ */
+public class FakeTimeZoneProviderRegistry {
+
+ private static final FakeTimeZoneProviderRegistry sInstance =
+ new FakeTimeZoneProviderRegistry();
+
+ private final Map<String, FakeTimeZoneProviderService> fakeTimeZoneProviderServiceMap =
+ new HashMap<>();
+
+ private FakeTimeZoneProviderRegistry() {
+ }
+
+ public static FakeTimeZoneProviderRegistry getInstance() {
+ return sInstance;
+ }
+
+ public synchronized void registerFakeTimeZoneProviderService(String id,
+ FakeTimeZoneProviderService fakeTimeZoneProviderService) {
+ fakeTimeZoneProviderServiceMap.put(id, fakeTimeZoneProviderService);
+ }
+
+ public synchronized FakeTimeZoneProviderService getFakeTimeZoneProviderService(String id) {
+ return fakeTimeZoneProviderServiceMap.get(id);
+ }
+}
diff --git a/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeLocationTimeZoneProviderService1.java b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeLocationTimeZoneProviderService1.java
new file mode 100644
index 0000000..6591168
--- /dev/null
+++ b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeLocationTimeZoneProviderService1.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.time.cts.fake_tzps_app.tzps;
+
+/** The fake primary location time zone provider. */
+public class FakeLocationTimeZoneProviderService1 extends FakeTimeZoneProviderService {
+
+ public FakeLocationTimeZoneProviderService1() {
+ super(FakeLocationTimeZoneProviderService1.class.getSimpleName());
+ }
+}
diff --git a/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeLocationTimeZoneProviderService2.java b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeLocationTimeZoneProviderService2.java
new file mode 100644
index 0000000..6bb7d36
--- /dev/null
+++ b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeLocationTimeZoneProviderService2.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.time.cts.fake_tzps_app.tzps;
+
+/** The fake secondary location time zone provider. */
+public class FakeLocationTimeZoneProviderService2 extends FakeTimeZoneProviderService {
+
+ public FakeLocationTimeZoneProviderService2() {
+ super(FakeLocationTimeZoneProviderService2.class.getSimpleName());
+ }
+}
diff --git a/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeTimeZoneProviderService.java b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeTimeZoneProviderService.java
new file mode 100644
index 0000000..36bd58f
--- /dev/null
+++ b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeTimeZoneProviderService.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.time.cts.fake_tzps_app.tzps;
+
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_CERTAIN;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_DISABLED;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_INITIALIZING;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_PERM_FAILED;
+
+import android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper;
+import android.os.SystemClock;
+import android.service.timezone.TimeZoneProviderService;
+import android.service.timezone.TimeZoneProviderSuggestion;
+
+import com.android.time.cts.fake_tzps_app.fixture.FakeTimeZoneProviderRegistry;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A base class for fake implementations of {@link TimeZoneProviderService} that can be queried /
+ * poked during tests. Each instance registers itself with {@link FakeTimeZoneProviderRegistry} on
+ * construction to enable interaction from tests.
+ */
+public class FakeTimeZoneProviderService extends TimeZoneProviderService {
+
+ private final String mId;
+ private int mState = PROVIDER_STATE_DISABLED;
+
+ protected FakeTimeZoneProviderService(String id) {
+ mId = Objects.requireNonNull(id);
+ FakeTimeZoneProviderRegistry.getInstance().registerFakeTimeZoneProviderService(id, this);
+ }
+
+ @Override
+ public void onStartUpdates(long initializationTimeoutMillis) {
+ mState = PROVIDER_STATE_INITIALIZING;
+ }
+
+ @Override
+ public void onStopUpdates() {
+ mState = PROVIDER_STATE_DISABLED;
+ }
+
+ // Fake behavior methods.
+ public int getState() {
+ return mState;
+ }
+
+ public void fakeReportUncertain() {
+ reportUncertain();
+ mState = FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_UNCERTAIN;
+ }
+
+ public void fakeReportPermanentFailure() {
+ reportPermanentFailure(new RuntimeException("Fake permanent failure"));
+ mState = PROVIDER_STATE_PERM_FAILED;
+ }
+
+ public void fakeReportSuggestion(List<String> timeZoneIds) {
+ TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+ .setTimeZoneIds(timeZoneIds)
+ .setElapsedRealtimeMillis(SystemClock.elapsedRealtime())
+ .build();
+ reportSuggestion(suggestion);
+ mState = PROVIDER_STATE_CERTAIN;
+ }
+}
diff --git a/hostsidetests/time/host/src/android/time/cts/host/BaseLocationTimeZoneManagerHostTest.java b/hostsidetests/time/host/src/android/time/cts/host/BaseLocationTimeZoneManagerHostTest.java
deleted file mode 100644
index 1cc28e6..0000000
--- a/hostsidetests/time/host/src/android/time/cts/host/BaseLocationTimeZoneManagerHostTest.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.time.cts.host;
-
-import static android.app.time.cts.shell.DeviceConfigKeys.NAMESPACE_SYSTEM_TIME;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.PRIMARY_PROVIDER_INDEX;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.PROVIDER_MODE_SIMULATED;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.SECONDARY_PROVIDER_INDEX;
-
-import android.app.time.LocationTimeZoneManagerServiceStateProto;
-import android.app.time.cts.shell.DeviceConfigShellHelper;
-import android.app.time.cts.shell.DeviceShellCommandExecutor;
-import android.app.time.cts.shell.LocationShellHelper;
-import android.app.time.cts.shell.LocationTimeZoneManagerShellHelper;
-import android.app.time.cts.shell.TimeZoneDetectorShellHelper;
-import android.app.time.cts.shell.host.HostShellCommandExecutor;
-
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-import com.google.protobuf.Parser;
-
-import org.junit.After;
-import org.junit.Before;
-
-/** A base class for tests that interact with the location_time_zone_manager via adb. */
-public abstract class BaseLocationTimeZoneManagerHostTest extends BaseHostJUnit4Test {
-
- private boolean mOriginalLocationEnabled;
-
- private boolean mOriginalAutoDetectionEnabled;
-
- private boolean mOriginalGeoDetectionEnabled;
-
- protected TimeZoneDetectorShellHelper mTimeZoneDetectorShellHelper;
- private LocationTimeZoneManagerShellHelper mLocationTimeZoneManagerShellHelper;
- private LocationShellHelper mLocationShellHelper;
- private DeviceConfigShellHelper mDeviceConfigShellHelper;
- private DeviceConfigShellHelper.PreTestState mDeviceConfigPreTestState;
-
- @Before
- public void setUp() throws Exception {
- DeviceShellCommandExecutor shellCommandExecutor = new HostShellCommandExecutor(getDevice());
- mTimeZoneDetectorShellHelper = new TimeZoneDetectorShellHelper(shellCommandExecutor);
- mLocationTimeZoneManagerShellHelper =
- new LocationTimeZoneManagerShellHelper(shellCommandExecutor);
- mLocationShellHelper = new LocationShellHelper(shellCommandExecutor);
- mDeviceConfigShellHelper = new DeviceConfigShellHelper(shellCommandExecutor);
-
- // Confirm the service being tested is present. It can be turned off, in which case there's
- // nothing to test.
- mLocationTimeZoneManagerShellHelper.assumeLocationTimeZoneManagerIsPresent();
-
- // All tests start with the location_time_zone_manager disabled so that providers can be
- // configured.
- stopLocationTimeZoneManagerService();
-
- mDeviceConfigPreTestState = mDeviceConfigShellHelper.setSyncModeForTest(
- DeviceConfigShellHelper.SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME);
-
- // Configure two simulated providers. At least one is needed to be able to turn on
- // geo detection below. Tests may override these values for their own use.
- setProviderModeOverride(PRIMARY_PROVIDER_INDEX, PROVIDER_MODE_SIMULATED);
- setProviderModeOverride(SECONDARY_PROVIDER_INDEX, PROVIDER_MODE_SIMULATED);
-
- // Make sure locations is enabled, otherwise the geo detection feature will be disabled
- // whatever the geolocation detection setting is set to.
- mOriginalLocationEnabled = mLocationShellHelper.isLocationEnabledForCurrentUser();
- if (!mOriginalLocationEnabled) {
- mLocationShellHelper.setLocationEnabledForCurrentUser(true);
- }
-
- // Make sure automatic time zone detection is enabled, otherwise the geo detection feature
- // will be disabled whatever the geolocation detection setting is set to
- mOriginalAutoDetectionEnabled = mTimeZoneDetectorShellHelper.isAutoDetectionEnabled();
- if (!mOriginalAutoDetectionEnabled) {
- mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(true);
- }
-
- // Make sure geolocation time zone detection is enabled.
- mOriginalGeoDetectionEnabled = mTimeZoneDetectorShellHelper.isGeoDetectionEnabled();
- if (!mOriginalGeoDetectionEnabled) {
- mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(true);
- }
- }
-
- @After
- public void tearDown() throws Exception {
- if (!mLocationTimeZoneManagerShellHelper.isLocationTimeZoneManagerPresent()) {
- // Setup didn't do anything, no need to tearDown.
- return;
- }
- // Turn off the service before we reset configuration, otherwise it will restart itself
- // repeatedly.
- stopLocationTimeZoneManagerService();
-
- // Reset settings and server flags as best we can.
- mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(mOriginalGeoDetectionEnabled);
- mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(mOriginalAutoDetectionEnabled);
- mLocationShellHelper.setLocationEnabledForCurrentUser(mOriginalLocationEnabled);
- setLocationTimeZoneManagerStateRecordingMode(false);
-
- mDeviceConfigShellHelper.restoreDeviceConfigStateForTest(mDeviceConfigPreTestState);
-
- // Attempt to start the service. It may not start if there are no providers configured,
- // but that is ok.
- startLocationTimeZoneManagerService();
- }
-
- protected LocationTimeZoneManagerServiceStateProto dumpLocationTimeZoneManagerServiceState()
- throws Exception {
- byte[] protoBytes = mLocationTimeZoneManagerShellHelper.dumpState();
- Parser<LocationTimeZoneManagerServiceStateProto> parser =
- LocationTimeZoneManagerServiceStateProto.parser();
- return parser.parseFrom(protoBytes);
- }
-
- protected void setLocationTimeZoneManagerStateRecordingMode(boolean enabled) throws Exception {
- mLocationTimeZoneManagerShellHelper.recordProviderStates(enabled);
- }
-
- protected void startLocationTimeZoneManagerService() throws Exception {
- mLocationTimeZoneManagerShellHelper.start();
- }
-
- protected void stopLocationTimeZoneManagerService() throws Exception {
- mLocationTimeZoneManagerShellHelper.stop();
- }
-
- protected void setProviderModeOverride(int providerIndex, String mode) throws Exception {
- mLocationTimeZoneManagerShellHelper.setProviderModeOverride(providerIndex, mode);
- }
-
- protected void simulateProviderSuggestion(int providerIndex, String... zoneIds)
- throws Exception {
- mLocationTimeZoneManagerShellHelper.simulateProviderSuggestion(providerIndex, zoneIds);
- }
-
- protected void simulateProviderBind(int providerIndex) throws Exception {
- mLocationTimeZoneManagerShellHelper.simulateProviderBind(providerIndex);
- }
-}
diff --git a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java
index 1894805..76f4723 100644
--- a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java
+++ b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java
@@ -17,48 +17,481 @@
package android.time.cts.host;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.PROVIDER_MODE_DISABLED;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.PROVIDER_MODE_SIMULATED;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.PRIMARY_PROVIDER_INDEX;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.SECONDARY_PROVIDER_INDEX;
+import static android.app.time.cts.shell.DeviceConfigKeys.LocationTimeZoneManager.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS;
+import static android.app.time.cts.shell.DeviceConfigKeys.NAMESPACE_SYSTEM_TIME;
+import static android.app.time.cts.shell.DeviceConfigShellHelper.SYNC_DISABLED_MODE_UNTIL_REBOOT;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.FAKE_TZPS_APP_APK;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.FAKE_TZPS_APP_PACKAGE;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_CERTAIN;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_DISABLED;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_INITIALIZING;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_UNCERTAIN;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import android.app.time.ControllerStateEnum;
import android.app.time.LocationTimeZoneManagerServiceStateProto;
import android.app.time.TimeZoneProviderStateEnum;
import android.app.time.TimeZoneProviderStateProto;
+import android.app.time.cts.shell.DeviceConfigShellHelper;
+import android.app.time.cts.shell.DeviceShellCommandExecutor;
+import android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper;
+import android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.FakeTimeZoneProviderShellHelper;
+import android.app.time.cts.shell.LocationShellHelper;
+import android.app.time.cts.shell.LocationTimeZoneManagerShellHelper;
+import android.app.time.cts.shell.TimeZoneDetectorShellHelper;
+import android.app.time.cts.shell.host.HostShellCommandExecutor;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.google.protobuf.Parser;
+
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.time.Duration;
import java.util.Arrays;
import java.util.List;
/** Host-side CTS tests for the location time zone manager service. */
@RunWith(DeviceJUnit4ClassRunner.class)
-public class LocationTimeZoneManagerHostTest extends BaseLocationTimeZoneManagerHostTest {
+public class LocationTimeZoneManagerHostTest extends BaseHostJUnit4Test {
+ private boolean mOriginalLocationEnabled;
+ private boolean mOriginalAutoDetectionEnabled;
+ private boolean mOriginalGeoDetectionEnabled;
+ private TimeZoneDetectorShellHelper mTimeZoneDetectorShellHelper;
+ private LocationTimeZoneManagerShellHelper mLocationTimeZoneManagerShellHelper;
+ private DeviceConfigShellHelper mDeviceConfigShellHelper;
+ private DeviceConfigShellHelper.PreTestState mDeviceConfigPreTestState;
+ private LocationShellHelper mLocationShellHelper;
+ private FakeTimeZoneProviderShellHelper mPrimaryFakeTimeZoneProviderShellHelper;
+ private FakeTimeZoneProviderShellHelper mSecondaryFakeTimeZoneProviderShellHelper;
+
+ @Before
+ public void setUp() throws Exception {
+ DeviceShellCommandExecutor shellCommandExecutor = new HostShellCommandExecutor(getDevice());
+ mLocationTimeZoneManagerShellHelper =
+ new LocationTimeZoneManagerShellHelper(shellCommandExecutor);
+
+ // Confirm the service being tested is present. It can be turned off, in which case there's
+ // nothing to test.
+ mLocationTimeZoneManagerShellHelper.assumeLocationTimeZoneManagerIsPresent();
+
+ // Install the app that hosts the fake providers.
+ // Installations are tracked in BaseHostJUnit4Test and uninstalled automatically.
+ installPackage(FAKE_TZPS_APP_APK);
+
+ mTimeZoneDetectorShellHelper = new TimeZoneDetectorShellHelper(shellCommandExecutor);
+ mLocationShellHelper = new LocationShellHelper(shellCommandExecutor);
+ mDeviceConfigShellHelper = new DeviceConfigShellHelper(shellCommandExecutor);
+
+ // Stop device_config updates for the duration of the test.
+ mDeviceConfigPreTestState = mDeviceConfigShellHelper.setSyncModeForTest(
+ SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME);
+
+ // All tests start with the location_time_zone_manager disabled so that providers can be
+ // configured.
+ mLocationTimeZoneManagerShellHelper.stop();
+
+ // Make sure locations is enabled, otherwise the geo detection feature will be disabled
+ // whatever the geolocation detection setting is set to.
+ mOriginalLocationEnabled = mLocationShellHelper.isLocationEnabledForCurrentUser();
+ if (!mOriginalLocationEnabled) {
+ mLocationShellHelper.setLocationEnabledForCurrentUser(true);
+ }
+
+ // Make sure automatic time zone detection is enabled, otherwise the geo detection feature
+ // will be disabled whatever the geolocation detection setting is set to.
+ mOriginalAutoDetectionEnabled = mTimeZoneDetectorShellHelper.isAutoDetectionEnabled();
+ if (!mOriginalAutoDetectionEnabled) {
+ mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(true);
+ }
+
+ // On devices with no location time zone providers (e.g. AOSP), we cannot turn geo detection
+ // on until the test LTZPs are configured as the time_zone_detector will refuse.
+ mOriginalGeoDetectionEnabled = mTimeZoneDetectorShellHelper.isGeoDetectionEnabled();
+
+ FakeTimeZoneProviderAppShellHelper fakeTimeZoneProviderAppShellHelper =
+ new FakeTimeZoneProviderAppShellHelper(shellCommandExecutor);
+ // Delay until the fake TZPS app can be found.
+ fakeTimeZoneProviderAppShellHelper.waitForInstallation();
+ mPrimaryFakeTimeZoneProviderShellHelper =
+ fakeTimeZoneProviderAppShellHelper.getPrimaryLocationProviderHelper();
+ mSecondaryFakeTimeZoneProviderShellHelper =
+ fakeTimeZoneProviderAppShellHelper.getSecondaryLocationProviderHelper();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (!mLocationTimeZoneManagerShellHelper.isLocationTimeZoneManagerPresent()) {
+ // Nothing to tear down.
+ return;
+ }
+
+ // Reset the geoDetectionEnabled state while there is at least one LTZP configured: this
+ // setting cannot be modified if there are no LTZPs on the device, e.g. on AOSP.
+ mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(mOriginalGeoDetectionEnabled);
+
+ // Turn off the service before we reset configuration, otherwise it will restart itself
+ // repeatedly.
+ mLocationTimeZoneManagerShellHelper.stop();
+
+ // Reset settings and server flags as best we can.
+ mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(mOriginalAutoDetectionEnabled);
+ mLocationShellHelper.setLocationEnabledForCurrentUser(mOriginalLocationEnabled);
+ mDeviceConfigShellHelper.restoreDeviceConfigStateForTest(mDeviceConfigPreTestState);
+
+ // Attempt to start the service. It may not start if there are no providers configured,
+ // but that is ok.
+ mLocationTimeZoneManagerShellHelper.start();
+ }
+
+ /** Tests what happens when there's only a primary provider and it makes a suggestion. */
@Test
- public void testSecondarySuggestion() throws Exception {
- setProviderModeOverride(PRIMARY_PROVIDER_INDEX, PROVIDER_MODE_DISABLED);
- setProviderModeOverride(SECONDARY_PROVIDER_INDEX, PROVIDER_MODE_SIMULATED);
- startLocationTimeZoneManagerService();
- setLocationTimeZoneManagerStateRecordingMode(true);
+ public void testOnlyPrimary_suggestionMade() throws Exception {
+ String testPrimaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
+ String testSecondaryLocationTimeZoneProviderPackageName = null;
+ mLocationTimeZoneManagerShellHelper.startWithTestProviders(
+ testPrimaryLocationTimeZoneProviderPackageName,
+ testSecondaryLocationTimeZoneProviderPackageName,
+ true /* recordProviderStates */);
+ mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(true);
+ mPrimaryFakeTimeZoneProviderShellHelper.assertCreated();
+ mSecondaryFakeTimeZoneProviderShellHelper.assertNotCreated();
- simulateProviderBind(SECONDARY_PROVIDER_INDEX);
- simulateProviderSuggestion(SECONDARY_PROVIDER_INDEX, "Europe/London");
+ {
+ LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+ assertControllerStateHistory(serviceState,
+ ControllerStateEnum.CONTROLLER_STATE_PROVIDERS_INITIALIZING,
+ ControllerStateEnum.CONTROLLER_STATE_STOPPED,
+ ControllerStateEnum.CONTROLLER_STATE_INITIALIZING);
+ assertNoLastSuggestion(serviceState);
+ assertProviderStates(serviceState.getPrimaryProviderStatesList(),
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED,
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_INITIALIZING);
+ mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_INITIALIZING);
- LocationTimeZoneManagerServiceStateProto serviceState =
- dumpLocationTimeZoneManagerServiceState();
- assertEquals(Arrays.asList("Europe/London"),
- serviceState.getLastSuggestion().getZoneIdsList());
+ assertProviderStates(serviceState.getSecondaryProviderStatesList(),
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED);
+ }
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
- List<TimeZoneProviderStateProto> secondaryStates =
- serviceState.getSecondaryProviderStatesList();
- assertEquals(1, secondaryStates.size());
- assertEquals(TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN,
- secondaryStates.get(0).getState());
+ mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/London");
+
+ {
+ LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+ assertControllerStateHistory(serviceState,
+ ControllerStateEnum.CONTROLLER_STATE_CERTAIN);
+ assertLastSuggestion(serviceState, "Europe/London");
+ assertProviderStates(serviceState.getPrimaryProviderStatesList(),
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN);
+ mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_CERTAIN);
+
+ assertProviderStates(serviceState.getSecondaryProviderStatesList());
+ }
+ }
+
+ /**
+ * Demonstrates that duplicate equivalent reports made by location time zone providers within
+ * a threshold time are ignored. It focuses on a single LTZP setup (primary only); the behavior
+ * for the secondary is assumed to be identical.
+ */
+ @Test
+ public void test_dupeSuggestionsMade_rateLimited() throws Exception {
+ // Set the rate setting sufficiently high that rate limiting will definitely take place.
+ mDeviceConfigShellHelper.put(NAMESPACE_SYSTEM_TIME,
+ KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS,
+ Long.toString(Duration.ofMinutes(10).toMillis()));
+
+ String testPrimaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
+ String testSecondaryLocationTimeZoneProviderPackageName = null;
+ mLocationTimeZoneManagerShellHelper.startWithTestProviders(
+ testPrimaryLocationTimeZoneProviderPackageName,
+ testSecondaryLocationTimeZoneProviderPackageName,
+ true /* recordProviderStates */);
+ mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(true);
+
+ mPrimaryFakeTimeZoneProviderShellHelper.assertCreated();
+ mSecondaryFakeTimeZoneProviderShellHelper.assertNotCreated();
+
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+ // Report a new time zone.
+ mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/London");
+ assertPrimaryReportedCertain();
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+ // Duplicate time zone suggestion.
+ mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/London");
+ assertPrimaryMadeNoReport();
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+ // Report a new time zone.
+ mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/Paris");
+ assertPrimaryReportedCertain();
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+ // Duplicate time zone suggestion.
+ mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/Paris");
+ assertPrimaryMadeNoReport();
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+ // Report uncertain.
+ mPrimaryFakeTimeZoneProviderShellHelper.reportUncertain();
+ assertPrimaryReportedUncertain();
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+ // Duplicate uncertain report.
+ mPrimaryFakeTimeZoneProviderShellHelper.reportUncertain();
+ assertPrimaryMadeNoReport();
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+ // Report a new time zone.
+ mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/Paris");
+ assertPrimaryReportedCertain();
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+ }
+
+ /**
+ * Demonstrates that duplicate equivalent reports made by location time zone providers above
+ * a threshold time are not filtered. It focuses on a single LTZP setup (primary only); the
+ * behavior for the secondary is assumed to be identical.
+ */
+ @Test
+ public void test_dupeSuggestionsMade_notRateLimited() throws Exception {
+ // Set the rate sufficiently low that rate limiting will not take place.
+ mDeviceConfigShellHelper.put(NAMESPACE_SYSTEM_TIME,
+ KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS,
+ "0");
+
+ String testPrimaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
+ String testSecondaryLocationTimeZoneProviderPackageName = null;
+ mLocationTimeZoneManagerShellHelper.startWithTestProviders(
+ testPrimaryLocationTimeZoneProviderPackageName,
+ testSecondaryLocationTimeZoneProviderPackageName,
+ true /* recordProviderStates */);
+ mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(true);
+ mPrimaryFakeTimeZoneProviderShellHelper.assertCreated();
+ mSecondaryFakeTimeZoneProviderShellHelper.assertNotCreated();
+
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+ // Report a new time zone.
+ mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/London");
+ assertPrimaryReportedCertain();
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+ // Duplicate time zone suggestion.
+ mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/London");
+ assertPrimaryReportedCertain();
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+ // Report uncertain.
+ mPrimaryFakeTimeZoneProviderShellHelper.reportUncertain();
+ assertPrimaryReportedUncertain();
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+ // Duplicate uncertain report.
+ mPrimaryFakeTimeZoneProviderShellHelper.reportUncertain();
+ assertPrimaryReportedUncertain();
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+ }
+
+ private void assertPrimaryReportedCertain() throws Exception {
+ LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+ assertProviderStates(serviceState.getPrimaryProviderStatesList(),
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN);
+ }
+
+ private void assertPrimaryMadeNoReport() throws Exception {
+ LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+ assertProviderStates(serviceState.getPrimaryProviderStatesList());
+ }
+
+ private void assertPrimaryReportedUncertain() throws Exception {
+ LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+ assertProviderStates(serviceState.getPrimaryProviderStatesList(),
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_UNCERTAIN);
+ }
+
+ /** Tests what happens when there's only a secondary provider and it makes a suggestion. */
+ @Test
+ public void testOnlySecondary_suggestionMade() throws Exception {
+ String testPrimaryLocationTimeZoneProviderPackageName = null;
+ String testSecondaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
+ mLocationTimeZoneManagerShellHelper.startWithTestProviders(
+ testPrimaryLocationTimeZoneProviderPackageName,
+ testSecondaryLocationTimeZoneProviderPackageName,
+ true /* recordProviderStates */);
+ mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(true);
+ mPrimaryFakeTimeZoneProviderShellHelper.assertNotCreated();
+ mSecondaryFakeTimeZoneProviderShellHelper.assertCreated();
+
+ {
+ LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+ assertControllerStateHistory(serviceState,
+ ControllerStateEnum.CONTROLLER_STATE_PROVIDERS_INITIALIZING,
+ ControllerStateEnum.CONTROLLER_STATE_STOPPED,
+ ControllerStateEnum.CONTROLLER_STATE_INITIALIZING);
+ assertNoLastSuggestion(serviceState);
+ assertProviderStates(serviceState.getPrimaryProviderStatesList(),
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED,
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_INITIALIZING,
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_PERM_FAILED);
+
+ assertProviderStates(serviceState.getSecondaryProviderStatesList(),
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED,
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_INITIALIZING);
+ }
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+ mSecondaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/London");
+
+ {
+ LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+ assertControllerStateHistory(serviceState,
+ ControllerStateEnum.CONTROLLER_STATE_CERTAIN);
+ assertLastSuggestion(serviceState, "Europe/London");
+ assertProviderStates(serviceState.getPrimaryProviderStatesList());
+
+ assertProviderStates(serviceState.getSecondaryProviderStatesList(),
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN);
+ mSecondaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_CERTAIN);
+ }
+ }
+
+ /**
+ * Tests what happens when there's both a primary and a secondary provider, the primary starts
+ * by being uncertain, the secondary makes a suggestion, then the primary makes a suggestion.
+ */
+ @Test
+ public void testPrimaryAndSecondary() throws Exception {
+ String testPrimaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
+ String testSecondaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
+ mLocationTimeZoneManagerShellHelper.startWithTestProviders(
+ testPrimaryLocationTimeZoneProviderPackageName,
+ testSecondaryLocationTimeZoneProviderPackageName,
+ true /* recordProviderStates*/);
+ mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(true);
+ mPrimaryFakeTimeZoneProviderShellHelper.assertCreated();
+ mSecondaryFakeTimeZoneProviderShellHelper.assertCreated();
+
+ {
+ LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+ assertControllerStateHistory(serviceState,
+ ControllerStateEnum.CONTROLLER_STATE_PROVIDERS_INITIALIZING,
+ ControllerStateEnum.CONTROLLER_STATE_STOPPED,
+ ControllerStateEnum.CONTROLLER_STATE_INITIALIZING);
+ assertNoLastSuggestion(serviceState);
+ assertProviderStates(serviceState.getPrimaryProviderStatesList(),
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED,
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_INITIALIZING);
+ mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_INITIALIZING);
+
+ assertProviderStates(serviceState.getSecondaryProviderStatesList(),
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED);
+ mSecondaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_DISABLED);
+ }
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+ // Make the primary report being uncertain. This should cause the secondary to be started.
+ mPrimaryFakeTimeZoneProviderShellHelper.reportUncertain();
+
+ {
+ LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+ assertControllerStateHistory(serviceState);
+ assertNoLastSuggestion(serviceState);
+ assertProviderStates(serviceState.getPrimaryProviderStatesList(),
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_UNCERTAIN);
+ mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_UNCERTAIN);
+
+ assertProviderStates(serviceState.getSecondaryProviderStatesList(),
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_INITIALIZING);
+ mSecondaryFakeTimeZoneProviderShellHelper.assertCurrentState(
+ PROVIDER_STATE_INITIALIZING);
+ }
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+ // Make the secondary report being certain.
+ mSecondaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/London");
+
+ {
+ LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+ assertControllerStateHistory(serviceState,
+ ControllerStateEnum.CONTROLLER_STATE_CERTAIN);
+ assertLastSuggestion(serviceState, "Europe/London");
+ assertProviderStates(serviceState.getPrimaryProviderStatesList());
+ mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_UNCERTAIN);
+
+ assertProviderStates(serviceState.getSecondaryProviderStatesList(),
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN);
+ mSecondaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_CERTAIN);
+ }
+ mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+ // Make the primary report being certain.
+ mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/Paris");
+
+ {
+ LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+ assertControllerStateHistory(serviceState);
+ assertLastSuggestion(serviceState, "Europe/Paris");
+ assertProviderStates(serviceState.getPrimaryProviderStatesList(),
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN);
+ mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_CERTAIN);
+
+ assertProviderStates(serviceState.getSecondaryProviderStatesList(),
+ TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED);
+ mSecondaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_DISABLED);
+ }
+ }
+
+ private static void assertControllerStateHistory(
+ LocationTimeZoneManagerServiceStateProto serviceState,
+ ControllerStateEnum... expectedStates) {
+ List<ControllerStateEnum> expectedStatesList = Arrays.asList(expectedStates);
+ List<ControllerStateEnum> actualStates = serviceState.getControllerStatesList();
+ assertEquals(expectedStatesList, actualStates);
+ }
+
+ private static void assertNoLastSuggestion(
+ LocationTimeZoneManagerServiceStateProto serviceState) {
+ if (serviceState.hasLastSuggestion()) {
+ fail("Expected no last suggestion, but found:" + serviceState.getLastSuggestion());
+ }
+ }
+
+ private static void assertLastSuggestion(LocationTimeZoneManagerServiceStateProto serviceState,
+ String... expectedTimeZones) {
+ assertFalse(expectedTimeZones == null || expectedTimeZones.length == 0);
+ assertTrue(serviceState.hasLastSuggestion());
+ List<String> expectedTimeZonesList = Arrays.asList(expectedTimeZones);
+ List<String> actualTimeZonesList = serviceState.getLastSuggestion().getZoneIdsList();
+ assertEquals(expectedTimeZonesList, actualTimeZonesList);
+ }
+
+ private static void assertProviderStates(List<TimeZoneProviderStateProto> actualStates,
+ TimeZoneProviderStateEnum... expectedStates) {
+ List<TimeZoneProviderStateEnum> expectedStatesList = Arrays.asList(expectedStates);
+ assertEquals("Expected states: " + expectedStatesList + ", but was " + actualStates,
+ expectedStatesList.size(), actualStates.size());
+ for (int i = 0; i < expectedStatesList.size(); i++) {
+ assertEquals("Expected states: " + expectedStatesList + ", but was " + actualStates,
+ expectedStates[i], actualStates.get(i).getState());
+ }
+ }
+
+ private LocationTimeZoneManagerServiceStateProto dumpServiceState() throws Exception {
+ byte[] protoBytes = mLocationTimeZoneManagerShellHelper.dumpState();
+ Parser<LocationTimeZoneManagerServiceStateProto> parser =
+ LocationTimeZoneManagerServiceStateProto.parser();
+ return parser.parseFrom(protoBytes);
}
}
diff --git a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerStatsTest.java b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerStatsTest.java
index d24b060..0be4769 100644
--- a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerStatsTest.java
+++ b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerStatsTest.java
@@ -16,13 +16,20 @@
package android.time.cts.host;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.PRIMARY_PROVIDER_INDEX;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.PROVIDER_MODE_DISABLED;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.PROVIDER_MODE_SIMULATED;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.SECONDARY_PROVIDER_INDEX;
+import static android.app.time.cts.shell.DeviceConfigKeys.NAMESPACE_SYSTEM_TIME;
+import static android.app.time.cts.shell.DeviceConfigShellHelper.SYNC_DISABLED_MODE_UNTIL_REBOOT;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.FAKE_TZPS_APP_APK;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.FAKE_TZPS_APP_PACKAGE;
import static java.util.stream.Collectors.toList;
+import android.app.time.cts.shell.DeviceConfigShellHelper;
+import android.app.time.cts.shell.DeviceShellCommandExecutor;
+import android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper;
+import android.app.time.cts.shell.LocationShellHelper;
+import android.app.time.cts.shell.LocationTimeZoneManagerShellHelper;
+import android.app.time.cts.shell.TimeZoneDetectorShellHelper;
+import android.app.time.cts.shell.host.HostShellCommandExecutor;
import android.cts.statsdatom.lib.AtomTestUtils;
import android.cts.statsdatom.lib.ConfigUtils;
import android.cts.statsdatom.lib.DeviceUtils;
@@ -31,7 +38,9 @@
import com.android.os.AtomsProto;
import com.android.os.AtomsProto.LocationTimeZoneProviderStateChanged;
import com.android.os.StatsLog;
+import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import org.junit.After;
import org.junit.Before;
@@ -46,54 +55,137 @@
/** Host-side CTS tests for the location time zone manager service stats logging. */
@RunWith(DeviceJUnit4ClassRunner.class)
-public class LocationTimeZoneManagerStatsTest extends BaseLocationTimeZoneManagerHostTest {
+public class LocationTimeZoneManagerStatsTest extends BaseHostJUnit4Test {
+
+ private static final int PRIMARY_PROVIDER_INDEX = 0;
+ private static final int SECONDARY_PROVIDER_INDEX = 1;
private static final int PROVIDER_STATES_COUNT =
LocationTimeZoneProviderStateChanged.State.values().length;
+ private TimeZoneDetectorShellHelper mTimeZoneDetectorShellHelper;
+ private LocationTimeZoneManagerShellHelper mLocationTimeZoneManagerShellHelper;
+ private LocationShellHelper mLocationShellHelper;
+ private DeviceConfigShellHelper mDeviceConfigShellHelper;
+ private DeviceConfigShellHelper.PreTestState mDeviceConfigPreTestState;
+
+ private boolean mOriginalLocationEnabled;
+ private boolean mOriginalAutoDetectionEnabled;
+ private boolean mOriginalGeoDetectionEnabled;
+
@Before
- @Override
public void setUp() throws Exception {
- super.setUp();
- ConfigUtils.removeConfig(getDevice());
- ReportUtils.clearReports(getDevice());
+ ITestDevice device = getDevice();
+ DeviceShellCommandExecutor shellCommandExecutor = new HostShellCommandExecutor(device);
+ mLocationTimeZoneManagerShellHelper =
+ new LocationTimeZoneManagerShellHelper(shellCommandExecutor);
+
+ // Confirm the service being tested is present. It can be turned off, in which case there's
+ // nothing to test.
+ mLocationTimeZoneManagerShellHelper.assumeLocationTimeZoneManagerIsPresent();
+
+ // Install the app that hosts the fake providers.
+ // Installations are tracked in BaseHostJUnit4Test and uninstalled automatically.
+ installPackage(FAKE_TZPS_APP_APK);
+
+ mTimeZoneDetectorShellHelper = new TimeZoneDetectorShellHelper(shellCommandExecutor);
+ mLocationShellHelper = new LocationShellHelper(shellCommandExecutor);
+ mDeviceConfigShellHelper = new DeviceConfigShellHelper(shellCommandExecutor);
+
+ mDeviceConfigPreTestState = mDeviceConfigShellHelper.setSyncModeForTest(
+ SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME);
+
+ // All tests start with the location_time_zone_manager disabled so that providers can be
+ // configured.
+ mLocationTimeZoneManagerShellHelper.stop();
+
+ // Make sure locations is enabled, otherwise the geo detection feature will be disabled
+ // whatever the geolocation detection setting is set to.
+ mOriginalLocationEnabled = mLocationShellHelper.isLocationEnabledForCurrentUser();
+ if (!mOriginalLocationEnabled) {
+ mLocationShellHelper.setLocationEnabledForCurrentUser(true);
+ }
+
+ // Make sure automatic time zone detection is enabled, otherwise the geo detection feature
+ // will be disabled whatever the geolocation detection setting is set to
+ mOriginalAutoDetectionEnabled = mTimeZoneDetectorShellHelper.isAutoDetectionEnabled();
+ if (!mOriginalAutoDetectionEnabled) {
+ mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(true);
+ }
+
+ // On devices with no location time zone providers (e.g. AOSP), we cannot turn geo detection
+ // on until the test LTZPs are configured as the time_zone_detector will refuse.
+ mOriginalGeoDetectionEnabled = mTimeZoneDetectorShellHelper.isGeoDetectionEnabled();
+
+ // Make sure that the fake providers used in the tests are available.
+ FakeTimeZoneProviderAppShellHelper fakeTimeZoneProviderAppShellHelper =
+ new FakeTimeZoneProviderAppShellHelper(shellCommandExecutor);
+ fakeTimeZoneProviderAppShellHelper.waitForInstallation();
+
+ ConfigUtils.removeConfig(device);
+ ReportUtils.clearReports(device);
}
@After
- @Override
public void tearDown() throws Exception {
+ if (!mLocationTimeZoneManagerShellHelper.isLocationTimeZoneManagerPresent()) {
+ // Nothing to tear down.
+ return;
+ }
+
+ // Reset the geoDetectionEnabled state while there is at least one LTZP configured: this
+ // setting cannot be modified if there are no LTZPs on the device, e.g. on AOSP.
+ mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(mOriginalGeoDetectionEnabled);
+
+ // Turn off the service before we reset configuration, otherwise it will restart itself
+ // repeatedly.
+ mLocationTimeZoneManagerShellHelper.stop();
+
+ // Reset settings and server flags as best we can.
+ mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(mOriginalAutoDetectionEnabled);
+ mLocationShellHelper.setLocationEnabledForCurrentUser(mOriginalLocationEnabled);
+
ConfigUtils.removeConfig(getDevice());
ReportUtils.clearReports(getDevice());
- super.tearDown();
+ mDeviceConfigShellHelper.restoreDeviceConfigStateForTest(mDeviceConfigPreTestState);
+
+ // Attempt to start the service. It may not start if there are no providers configured,
+ // but that is ok.
+ mLocationTimeZoneManagerShellHelper.start();
}
@Test
public void testAtom_locationTimeZoneProviderStateChanged() throws Exception {
- setProviderModeOverride(PRIMARY_PROVIDER_INDEX, PROVIDER_MODE_DISABLED);
- setProviderModeOverride(SECONDARY_PROVIDER_INDEX, PROVIDER_MODE_SIMULATED);
- mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(false);
-
- startLocationTimeZoneManagerService();
-
ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
AtomsProto.Atom.LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED_FIELD_NUMBER);
+ String testPrimaryLocationTimeZoneProviderPackageName = null;
+ String testSecondaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
+ mLocationTimeZoneManagerShellHelper.startWithTestProviders(
+ testPrimaryLocationTimeZoneProviderPackageName,
+ testSecondaryLocationTimeZoneProviderPackageName,
+ true /* recordProviderStates */);
+
// Turn geo detection on and off, twice.
for (int i = 0; i < 2; i++) {
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(true);
Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(false);
- Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
}
// Sorted list of events in order in which they occurred.
List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
// States.
+ Set<Integer> primaryProviderCreated = singletonStateId(PRIMARY_PROVIDER_INDEX,
+ LocationTimeZoneProviderStateChanged.State.STOPPED);
Set<Integer> primaryProviderStarted = singletonStateId(PRIMARY_PROVIDER_INDEX,
LocationTimeZoneProviderStateChanged.State.INITIALIZING);
Set<Integer> primaryProviderFailed = singletonStateId(PRIMARY_PROVIDER_INDEX,
LocationTimeZoneProviderStateChanged.State.PERM_FAILED);
+ Set<Integer> secondaryProviderCreated = singletonStateId(SECONDARY_PROVIDER_INDEX,
+ LocationTimeZoneProviderStateChanged.State.STOPPED);
Set<Integer> secondaryProviderStarted = singletonStateId(SECONDARY_PROVIDER_INDEX,
LocationTimeZoneProviderStateChanged.State.INITIALIZING);
Set<Integer> secondaryProviderStopped = singletonStateId(SECONDARY_PROVIDER_INDEX,
@@ -108,21 +200,12 @@
// Assert that the events happened in the expected order. This does not check "wait" (the
// time between events).
List<Set<Integer>> stateSets = Arrays.asList(
+ primaryProviderCreated, secondaryProviderCreated,
primaryProviderStarted, primaryProviderFailed,
secondaryProviderStarted, secondaryProviderStopped,
secondaryProviderStarted, secondaryProviderStopped);
- AtomTestUtils.assertStatesOccurred(stateSets, data,
+ AtomTestUtils.assertStatesOccurredInOrder(stateSets, data,
0 /* wait */, eventToStateFunction);
-
- // Assert that the events for the secondary provider happened in the expected order. This
- // does check "wait" (the time between events).
- List<StatsLog.EventMetricData> secondaryEvents =
- extractEventsForProviderIndex(data, SECONDARY_PROVIDER_INDEX);
- List<Set<Integer>> secondaryStateSets = Arrays.asList(
- secondaryProviderStarted, secondaryProviderStopped,
- secondaryProviderStarted, secondaryProviderStopped);
- AtomTestUtils.assertStatesOccurred(secondaryStateSets, secondaryEvents,
- AtomTestUtils.WAIT_TIME_SHORT /* wait */, eventToStateFunction);
}
private static Set<Integer> singletonStateId(int providerIndex,
diff --git a/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetectorStatsTest.java b/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetectorStatsTest.java
index 92992f7..4b8a83c 100644
--- a/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetectorStatsTest.java
+++ b/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetectorStatsTest.java
@@ -17,6 +17,7 @@
package android.time.cts.host;
import static android.app.time.cts.shell.DeviceConfigKeys.NAMESPACE_SYSTEM_TIME;
+import static android.app.time.cts.shell.DeviceConfigShellHelper.SYNC_DISABLED_MODE_UNTIL_REBOOT;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -24,8 +25,8 @@
import android.app.time.cts.shell.DeviceConfigShellHelper;
import android.app.time.cts.shell.DeviceShellCommandExecutor;
import android.app.time.cts.shell.LocationShellHelper;
-import android.app.time.cts.shell.host.HostShellCommandExecutor;
import android.app.time.cts.shell.TimeZoneDetectorShellHelper;
+import android.app.time.cts.shell.host.HostShellCommandExecutor;
import android.cts.statsdatom.lib.AtomTestUtils;
import android.cts.statsdatom.lib.ConfigUtils;
import android.cts.statsdatom.lib.DeviceUtils;
@@ -59,7 +60,7 @@
mLocationShellHelper = new LocationShellHelper(shellCommandExecutor);
mDeviceConfigShellHelper = new DeviceConfigShellHelper(shellCommandExecutor);
mDeviceConfigPreTestState = mDeviceConfigShellHelper.setSyncModeForTest(
- DeviceConfigShellHelper.SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME);
+ SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME);
ConfigUtils.removeConfig(getDevice());
ReportUtils.clearReports(getDevice());
diff --git a/libs/input/src/com/android/cts/input/UinputDevice.java b/libs/input/src/com/android/cts/input/UinputDevice.java
index 5a1441c..b2db4e0 100644
--- a/libs/input/src/com/android/cts/input/UinputDevice.java
+++ b/libs/input/src/com/android/cts/input/UinputDevice.java
@@ -75,6 +75,19 @@
}
/**
+ * Create Uinput device using the provided resourceId.
+ */
+ public static UinputDevice create(Instrumentation instrumentation, int resourceId,
+ int sources) {
+ final InputJsonParser parser = new InputJsonParser(instrumentation.getTargetContext());
+ final int resourceDeviceId = parser.readDeviceId(resourceId);
+ final String registerCommand = parser.readRegisterCommand(resourceId);
+ return new UinputDevice(instrumentation, resourceDeviceId,
+ parser.readVendorId(resourceId), parser.readProductId(resourceId),
+ sources, registerCommand);
+ }
+
+ /**
* Get uinput command return results as list of UinputResultData
*
* @return List of UinputResultData results
diff --git a/libs/input/src/com/android/cts/input/VirtualInputDevice.java b/libs/input/src/com/android/cts/input/VirtualInputDevice.java
index 5c8879c..2fd4154 100644
--- a/libs/input/src/com/android/cts/input/VirtualInputDevice.java
+++ b/libs/input/src/com/android/cts/input/VirtualInputDevice.java
@@ -198,6 +198,18 @@
return mDeviceId;
}
+ public int getRegisterCommandDeviceId() {
+ return mId;
+ }
+
+ public int getVendorId() {
+ return mVendorId;
+ }
+
+ public int getProductId() {
+ return mProductId;
+ }
+
private void setupPipes() {
UiAutomation ui = mInstrumentation.getUiAutomation();
ParcelFileDescriptor[] pipes = ui.executeShellCommandRw(getShellCommand());
diff --git a/libs/install/Android.bp b/libs/install/Android.bp
index 4e2c335..31784e4 100644
--- a/libs/install/Android.bp
+++ b/libs/install/Android.bp
@@ -132,6 +132,31 @@
apex_available: [ "com.android.apex.apkrollback.test_v2" ],
}
+android_test_helper_app {
+ name: "TestAppARollbackWipeV2",
+ manifest: "testapp/ARollbackWipeV2.xml",
+ sdk_version: "current",
+ srcs: ["testapp/src/**/*.java"],
+ resource_dirs: ["testapp/res_v2"],
+ apex_available: [ "com.android.apex.apkrollback.test_v2" ],
+}
+
+android_test_helper_app {
+ name: "TestAppBRollbackRestoreV2",
+ manifest: "testapp/BRollbackRestoreV2.xml",
+ sdk_version: "current",
+ srcs: ["testapp/src/**/*.java"],
+ resource_dirs: ["testapp/res_v2"],
+}
+
+android_test_helper_app {
+ name: "TestAppCRollbackRetainV2",
+ manifest: "testapp/CRollbackRetainV2.xml",
+ sdk_version: "current",
+ srcs: ["testapp/src/**/*.java"],
+ resource_dirs: ["testapp/res_v2"],
+}
+
java_library {
name: "cts-install-lib-java",
srcs: ["src/**/lib/*.java"],
@@ -156,6 +181,9 @@
":TestAppASplitV2",
":TestAppAOriginalV1",
":TestAppARotatedV2",
+ ":TestAppARollbackWipeV2",
+ ":TestAppBRollbackRestoreV2",
+ ":TestAppCRollbackRetainV2",
":StagedInstallTestApexV1",
":StagedInstallTestApexV2",
":StagedInstallTestApexV3",
diff --git a/libs/install/src/com/android/cts/install/lib/Install.java b/libs/install/src/com/android/cts/install/lib/Install.java
index 377e88a2..86113e6 100644
--- a/libs/install/src/com/android/cts/install/lib/Install.java
+++ b/libs/install/src/com/android/cts/install/lib/Install.java
@@ -20,6 +20,7 @@
import android.content.Intent;
import android.content.pm.PackageInstaller;
+import android.text.TextUtils;
import com.android.compatibility.common.util.SystemUtil;
@@ -38,6 +39,7 @@
// Indicates whether Install represents a multiPackage install.
private final boolean mIsMultiPackage;
// PackageInstaller.Session parameters.
+ private String mPackageName = null;
private boolean mIsStaged = false;
private boolean mIsDowngrade = false;
private boolean mEnableRollback = false;
@@ -96,6 +98,14 @@
}
/**
+ * Sets package name to the session params.
+ */
+ public Install setPackageName(String packageName) {
+ mPackageName = packageName;
+ return this;
+ }
+
+ /**
* Makes the install a staged install.
*/
public Install setStaged() {
@@ -227,6 +237,9 @@
try {
PackageInstaller.SessionParams params =
new PackageInstaller.SessionParams(mSessionMode);
+ if (!TextUtils.isEmpty(mPackageName)) {
+ params.setAppPackageName(mPackageName);
+ }
if (multiPackage) {
params.setMultiPackage();
}
diff --git a/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java b/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
index cdf709c..85b1ac6 100644
--- a/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
+++ b/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
@@ -54,7 +54,8 @@
Context context = InstrumentationRegistry.getTargetContext();
// Generate a unique string to ensure each LocalIntentSender gets its own results.
String action = LocalIntentSender.class.getName() + SystemClock.elapsedRealtime();
- context.registerReceiver(this, new IntentFilter(action));
+ context.registerReceiver(this, new IntentFilter(action),
+ Context.RECEIVER_EXPORTED_UNAUDITED);
Intent intent = new Intent(action);
PendingIntent pending = PendingIntent.getBroadcast(context, 0, intent, FLAG_MUTABLE);
return pending.getIntentSender();
diff --git a/libs/install/src/com/android/cts/install/lib/PackageInstallerSessionInfoSubject.java b/libs/install/src/com/android/cts/install/lib/PackageInstallerSessionInfoSubject.java
new file mode 100644
index 0000000..74c75eb
--- /dev/null
+++ b/libs/install/src/com/android/cts/install/lib/PackageInstallerSessionInfoSubject.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.install.lib;
+
+import android.content.pm.PackageInstaller;
+
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import javax.annotation.Nullable;
+
+public final class PackageInstallerSessionInfoSubject extends Subject {
+ private final PackageInstaller.SessionInfo mActual;
+
+ private PackageInstallerSessionInfoSubject(FailureMetadata failureMetadata,
+ @Nullable PackageInstaller.SessionInfo subject) {
+ super(failureMetadata, subject);
+ mActual = subject;
+ }
+
+ private static Subject.Factory<PackageInstallerSessionInfoSubject,
+ PackageInstaller.SessionInfo> sessions() {
+ return new Subject.Factory<PackageInstallerSessionInfoSubject,
+ PackageInstaller.SessionInfo>() {
+ @Override
+ public PackageInstallerSessionInfoSubject createSubject(FailureMetadata failureMetadata,
+ PackageInstaller.SessionInfo session) {
+ return new PackageInstallerSessionInfoSubject(failureMetadata, session);
+ }
+ };
+ }
+
+ public static PackageInstallerSessionInfoSubject assertThat(
+ PackageInstaller.SessionInfo session) {
+ return Truth.assertAbout(sessions()).that(session);
+ }
+
+ public void isStagedSessionReady() {
+ check(failureMessage("in state READY")).that(mActual.isStagedSessionReady()).isTrue();
+ }
+
+ public void isStagedSessionApplied() {
+ check(failureMessage("in state APPLIED")).that(mActual.isStagedSessionApplied()).isTrue();
+ }
+
+ public void isStagedSessionFailed() {
+ check(failureMessage("in state FAILED")).that(mActual.isStagedSessionFailed()).isTrue();
+ }
+
+ private String failureMessage(String suffix) {
+ return String.format("Not true that session %s is %s", subjectAsString(), suffix);
+ }
+
+ private String subjectAsString() {
+ return "{" + "appPackageName = " + mActual.getAppPackageName() + "; "
+ + "sessionId = " + mActual.getSessionId() + "; "
+ + "isStagedSessionReady = " + mActual.isStagedSessionReady() + "; "
+ + "isStagedSessionApplied = " + mActual.isStagedSessionApplied() + "; "
+ + "isStagedSessionFailed = " + mActual.isStagedSessionFailed() + "; "
+ + "stagedSessionErrorMessage = " + mActual.getStagedSessionErrorMessage() + "}";
+ }
+}
diff --git a/libs/install/src/com/android/cts/install/lib/TestApp.java b/libs/install/src/com/android/cts/install/lib/TestApp.java
index 8775ef4..8e550d9 100644
--- a/libs/install/src/com/android/cts/install/lib/TestApp.java
+++ b/libs/install/src/com/android/cts/install/lib/TestApp.java
@@ -54,6 +54,8 @@
"TestAppAOriginalV1.apk");
public static final TestApp ARotated2 = new TestApp("ARotatedV2", A, 2, /*isApex*/false,
"TestAppARotatedV2.apk");
+ public static final TestApp ARollbackWipe2 = new TestApp("ARollbackWipe2", A, 2,
+ /*isApex*/false, "TestAppARollbackWipeV2.apk");
public static final TestApp B1 = new TestApp("Bv1", B, 1, /*isApex*/false,
"TestAppBv1.apk");
@@ -61,11 +63,15 @@
"TestAppBv2.apk");
public static final TestApp B3 = new TestApp("Bv3", B, 3, /*isApex*/false,
"TestAppBv3.apk");
+ public static final TestApp BRollbackRestore2 = new TestApp("BRollbackRestore2", B, 2,
+ /*isApex*/false, "TestAppBRollbackRestoreV2.apk");
public static final TestApp C1 = new TestApp("Cv1", C, 1, /*isApex*/false,
"TestAppCv1.apk");
public static final TestApp C2 = new TestApp("Cv2", C, 2, /*isApex*/false,
"TestAppCv2.apk");
+ public static final TestApp CRollbackRetain2 = new TestApp("CRollbackRetain2", C, 2,
+ /*isApex*/false, "TestAppCRollbackRetainV2.apk");
// Apex collection
public static final TestApp Apex1 = new TestApp("Apex1", SHIM_APEX_PACKAGE_NAME, 1,
diff --git a/libs/install/testapp/ARollbackWipeV2.xml b/libs/install/testapp/ARollbackWipeV2.xml
new file mode 100644
index 0000000..047efb5
--- /dev/null
+++ b/libs/install/testapp/ARollbackWipeV2.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.install.lib.testapp.A"
+ android:versionCode="2"
+ android:versionName="2.0">
+
+
+ <uses-sdk android:minSdkVersion="19"/>
+
+ <application android:label="Test App A2"
+ android:rollbackDataPolicy="wipe">
+ <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+ android:exported="true"/>
+ <activity android:name="com.android.cts.install.lib.testapp.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/libs/install/testapp/BRollbackRestoreV2.xml b/libs/install/testapp/BRollbackRestoreV2.xml
new file mode 100644
index 0000000..29aa674
--- /dev/null
+++ b/libs/install/testapp/BRollbackRestoreV2.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.install.lib.testapp.B"
+ android:versionCode="2"
+ android:versionName="2.0">
+
+
+ <uses-sdk android:minSdkVersion="19"/>
+
+ <application android:label="Test App B2"
+ android:rollbackDataPolicy="restore">
+ <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+ android:exported="true"/>
+ <activity android:name="com.android.cts.install.lib.testapp.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/libs/install/testapp/CRollbackRetainV2.xml b/libs/install/testapp/CRollbackRetainV2.xml
new file mode 100644
index 0000000..d1d168d
--- /dev/null
+++ b/libs/install/testapp/CRollbackRetainV2.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.install.lib.testapp.C"
+ android:versionCode="2"
+ android:versionName="2.0">
+
+
+ <uses-sdk android:minSdkVersion="19"/>
+
+ <application android:label="Test App C2"
+ android:rollbackDataPolicy="retain">
+ <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+ android:exported="true"/>
+ <activity android:name="com.android.cts.install.lib.testapp.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/libs/install/testapp/Cv2.xml b/libs/install/testapp/Cv2.xml
index 0d98b63..cc13ed5 100644
--- a/libs/install/testapp/Cv2.xml
+++ b/libs/install/testapp/Cv2.xml
@@ -23,8 +23,7 @@
<uses-sdk android:minSdkVersion="28"/>
- <application android:label="Test App C2"
- android:rollbackDataPolicy="wipe">
+ <application android:label="Test App C2">
<receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
android:exported="true"/>
<activity android:name="com.android.cts.install.lib.testapp.MainActivity"
diff --git a/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java b/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
index c1de522..595dd8be 100644
--- a/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
+++ b/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
@@ -266,7 +266,8 @@
latch.countDown();
}
};
- context.registerReceiver(crashReceiver, crashFilter);
+ context.registerReceiver(crashReceiver, crashFilter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
// Launch the app.
Intent intent = new Intent(Intent.ACTION_MAIN);
diff --git a/tests/AlarmManager/app/AndroidManifest.xml b/tests/AlarmManager/app/AndroidManifest.xml
index 9725080..618a1a0 100644
--- a/tests/AlarmManager/app/AndroidManifest.xml
+++ b/tests/AlarmManager/app/AndroidManifest.xml
@@ -31,6 +31,7 @@
where this is not required -->
<intent-filter>
<action android:name="android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED" />
+ <action android:name="android.app.action.cts.ACTION_PING" />
</intent-filter>
</receiver>
<service android:name=".TestService"
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/ExactAlarmsTest.java b/tests/AlarmManager/src/android/alarmmanager/cts/ExactAlarmsTest.java
index a556c4e..9615cad 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/ExactAlarmsTest.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/ExactAlarmsTest.java
@@ -530,7 +530,7 @@
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP));
Log.d(TAG, "Un-force-stoppping the test app");
- Intent i = new Intent("ACTION_PING"); // any action
+ Intent i = new Intent("android.app.action.cts.ACTION_PING");
i.setComponent(mPermissionChangeReceiver);
i.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
sContext.sendBroadcast(i);
diff --git a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
index 9fa8601..618c324 100644
--- a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
+++ b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
@@ -33,6 +33,7 @@
import junit.framework.Assert;
import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -76,6 +77,9 @@
public boolean onStartJob(JobParameters params) {
Log.i(TAG, "Test job executing: " + params.getJobId());
mParams = params;
+ TestEnvironment.getTestEnvironment().addEvent(
+ new TestEnvironment.Event(
+ TestEnvironment.Event.EVENT_START_JOB, params.getJobId()));
int permCheckRead = PackageManager.PERMISSION_DENIED;
int permCheckWrite = PackageManager.PERMISSION_DENIED;
@@ -380,6 +384,7 @@
private ArrayList<JobWorkItem> mExecutedReceivedWork;
private String mExecutedErrorMessage;
private JobParameters mStopJobParameters;
+ private List<Event> mExecutedEvents = new ArrayList<>();
public static TestEnvironment getTestEnvironment() {
if (kTestEnvironment == null) {
@@ -466,7 +471,6 @@
private void notifyExecution(JobParameters params, int permCheckRead, int permCheckWrite,
ArrayList<JobWorkItem> receivedWork, String errorMsg) {
- //Log.d(TAG, "Job executed:" + params.getJobId());
mExecutedJobParameters = params;
mExecutedPermCheckRead = permCheckRead;
mExecutedPermCheckWrite = permCheckWrite;
@@ -501,6 +505,7 @@
mDoWorkLatch = null;
mExpectedWork = null;
mContinueAfterStart = false;
+ mExecutedEvents.clear();
}
public void setExpectedWaitForStop() {
@@ -545,5 +550,46 @@
mStopJobParameters = null;
}
+ void addEvent(Event event) {
+ mExecutedEvents.add(event);
+ }
+
+ public List<Event> getExecutedEvents() {
+ return mExecutedEvents;
+ }
+
+ public static class Event {
+ public static final int EVENT_START_JOB = 0;
+
+ public int event;
+ public int jobId;
+
+ public Event(int event, int jobId) {
+ this.event = event;
+ this.jobId = jobId;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other instanceof Event) {
+ Event otherEvent = (Event) other;
+ return otherEvent.event == event && otherEvent.jobId == jobId;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return event + 31 * jobId;
+ }
+
+ @Override
+ public String toString() {
+ return "Event{" + event + ", " + jobId + "}";
+ }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java
index a92f8ac..7e78e2d 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java
@@ -15,6 +15,8 @@
*/
package android.jobscheduler.cts;
+import static com.android.compatibility.common.util.TestUtils.waitUntil;
+
import android.annotation.CallSuper;
import android.annotation.TargetApi;
import android.app.Instrumentation;
@@ -217,6 +219,37 @@
Thread.sleep(2_000);
}
+ void setBatteryState(boolean plugged, int level) throws Exception {
+ if (plugged) {
+ SystemUtil.runShellCommand(getInstrumentation(), "cmd battery set ac 1");
+ final int curLevel = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
+ "dumpsys battery get level").trim());
+ if (curLevel >= level) {
+ // Lower the level so when we set it to the desired level, JobScheduler thinks
+ // the device is charging.
+ SystemUtil.runShellCommand(getInstrumentation(),
+ "cmd battery set level " + Math.max(1, level - 1));
+ }
+ } else {
+ SystemUtil.runShellCommand(getInstrumentation(), "cmd battery unplug");
+ }
+ int seq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
+ "cmd battery set -f level " + level).trim());
+
+ // Wait for the battery update to be processed by job scheduler before proceeding.
+ waitUntil("JobScheduler didn't update charging status to " + plugged, 15 /* seconds */,
+ () -> {
+ int curSeq;
+ boolean curCharging;
+ curSeq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
+ "cmd jobscheduler get-battery-seq").trim());
+ curCharging = Boolean.parseBoolean(
+ SystemUtil.runShellCommand(getInstrumentation(),
+ "cmd jobscheduler get-battery-charging").trim());
+ return curSeq >= seq && curCharging == plugged;
+ });
+ }
+
/** Asks (not forces) JobScheduler to run the job if constraints are met. */
void runSatisfiedJob(int jobId) throws Exception {
SystemUtil.runShellCommand(getInstrumentation(),
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
index 3974c38..f6ff23f 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
@@ -22,7 +22,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
-import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
@@ -92,34 +91,6 @@
return present;
}
- void setBatteryState(boolean plugged, int level) throws Exception {
- if (plugged) {
- SystemUtil.runShellCommand(getInstrumentation(), "cmd battery set ac 1");
- } else {
- SystemUtil.runShellCommand(getInstrumentation(), "cmd battery unplug");
- }
- int seq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
- "cmd battery set -f level " + level).trim());
- long startTime = SystemClock.elapsedRealtime();
-
- // Wait for the battery update to be processed by job scheduler before proceeding.
- int curSeq;
- boolean curCharging;
- do {
- Thread.sleep(50);
- curSeq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
- "cmd jobscheduler get-battery-seq").trim());
- curCharging = Boolean.parseBoolean(SystemUtil.runShellCommand(getInstrumentation(),
- "cmd jobscheduler get-battery-charging").trim());
- if (curSeq >= seq && curCharging == plugged) {
- return;
- }
- } while ((SystemClock.elapsedRealtime() - startTime) < 5000);
-
- fail("Timed out waiting for job scheduler: expected seq=" + seq + ", cur=" + curSeq
- + ", expected plugged=" + plugged + " curCharging=" + curCharging);
- }
-
void verifyChargingState(boolean charging) throws Exception {
boolean curCharging = Boolean.parseBoolean(SystemUtil.runShellCommand(getInstrumentation(),
"cmd jobscheduler get-battery-charging").trim());
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
index b440d83..9b0af4d 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
@@ -937,16 +937,14 @@
/**
* Ensure WiFi is enabled, and block until we've verified that we are in fact connected.
*/
- private void connectToWifi()
- throws InterruptedException {
+ private void connectToWifi() throws Exception {
setWifiState(true, mCm, mWifiManager);
}
/**
* Ensure WiFi is disabled, and block until we've verified that we are in fact disconnected.
*/
- private void disconnectFromWifi()
- throws InterruptedException {
+ private void disconnectFromWifi() throws Exception {
setWifiState(false, mCm, mWifiManager);
}
@@ -964,7 +962,7 @@
* Taken from {@link android.net.http.cts.ApacheHttpClientTest}.
*/
static void setWifiState(final boolean enable,
- final ConnectivityManager cm, final WifiManager wm) throws InterruptedException {
+ final ConnectivityManager cm, final WifiManager wm) throws Exception {
if (enable != isWiFiConnected(cm, wm)) {
NetworkRequest nr = new NetworkRequest.Builder().clearCapabilities().build();
NetworkCapabilities nc = new NetworkCapabilities.Builder()
@@ -975,6 +973,7 @@
if (enable) {
SystemUtil.runShellCommand("svc wifi enable");
+ waitUntil("Failed to enable Wifi", 30 /* seconds */, () -> wm.isWifiEnabled());
//noinspection deprecation
SystemUtil.runWithShellPermissionIdentity(wm::reconnect,
android.Manifest.permission.NETWORK_SETTINGS);
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
index eece252..2c54508 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
@@ -164,6 +164,7 @@
// Test all allowed constraints.
JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setExpedited(true)
+ .setPriority(JobInfo.PRIORITY_HIGH)
.setPersisted(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setRequiresStorageNotLow(true)
@@ -172,6 +173,14 @@
// Confirm JobScheduler accepts the JobInfo object.
mJobScheduler.schedule(ji);
+ // Confirm default priority for EJs.
+ ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setExpedited(true)
+ .build();
+ assertEquals(JobInfo.PRIORITY_MAX, ji.getPriority());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
+
// Test disallowed constraints.
final String failureMessage =
"Successfully built an expedited JobInfo object with disallowed constraints";
@@ -190,6 +199,14 @@
assertBuildFails(failureMessage,
new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setExpedited(true)
+ .setPriority(JobInfo.PRIORITY_LOW));
+ assertBuildFails(failureMessage,
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setExpedited(true)
+ .setPriority(JobInfo.PRIORITY_DEFAULT));
+ assertBuildFails(failureMessage,
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setExpedited(true)
.setImportantWhileForeground(true));
assertBuildFails(failureMessage,
new JobInfo.Builder(JOB_ID, kJobServiceComponent)
@@ -216,6 +233,7 @@
.addTriggerContentUri(tcu));
}
+ @SuppressWarnings("deprecation")
public void testImportantWhileForeground() {
// Assert the default value is false
JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
@@ -228,6 +246,7 @@
.setImportantWhileForeground(true)
.build();
assertTrue(ji.isImportantWhileForeground());
+ assertEquals(JobInfo.PRIORITY_HIGH, ji.getPriority());
// Confirm JobScheduler accepts the JobInfo object.
mJobScheduler.schedule(ji);
@@ -239,6 +258,55 @@
mJobScheduler.schedule(ji);
}
+ public void testMinimumChunkSizeBytes() {
+ assertBuildFails(
+ "Successfully built a JobInfo specifying minimum chunk bytes without"
+ + " requesting network",
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setMinimumNetworkChunkBytes(500));
+ try {
+ assertBuildFails(
+ "Successfully built a JobInfo specifying minimum chunk bytes a negative value",
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setMinimumNetworkChunkBytes(-500));
+ } catch (IllegalArgumentException expected) {
+ // Success. setMinimumNetworkChunkBytes() should throw the exception.
+ }
+
+ assertBuildFails(
+ "Successfully built a JobInfo with a higher minimum chunk size than total"
+ + " transfer size",
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setMinimumNetworkChunkBytes(500)
+ .setEstimatedNetworkBytes(5, 5));
+ assertBuildFails(
+ "Successfully built a JobInfo with a higher minimum chunk size than total"
+ + " transfer size",
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setMinimumNetworkChunkBytes(500)
+ .setEstimatedNetworkBytes(JobInfo.NETWORK_BYTES_UNKNOWN, 5));
+ assertBuildFails(
+ "Successfully built a JobInfo with a higher minimum chunk size than total"
+ + " transfer size",
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setMinimumNetworkChunkBytes(500)
+ .setEstimatedNetworkBytes(5, JobInfo.NETWORK_BYTES_UNKNOWN));
+
+ JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setMinimumNetworkChunkBytes(500)
+ .setEstimatedNetworkBytes(
+ JobInfo.NETWORK_BYTES_UNKNOWN, JobInfo.NETWORK_BYTES_UNKNOWN)
+ .build();
+ assertEquals(500, ji.getMinimumNetworkChunkBytes());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
+ }
+
public void testMinimumLatency() {
JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setMinimumLatency(1337)
@@ -320,6 +388,85 @@
assertFalse(ji.isPrefetch());
// Confirm JobScheduler accepts the JobInfo object.
mJobScheduler.schedule(ji);
+
+ ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setMinimumLatency(60_000L)
+ .setPrefetch(true)
+ .build();
+ assertTrue(ji.isPrefetch());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
+
+ // CTS naturally targets latest SDK version. Compat change should be enabled by default.
+ assertBuildFails("Modern prefetch jobs can't have a deadline",
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setMinimumLatency(60_000L)
+ .setOverrideDeadline(600_000L)
+ .setPrefetch(true));
+ }
+
+ public void testPriority() {
+ // Assert the default value is DEFAULT
+ JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .build();
+ assertEquals(JobInfo.PRIORITY_DEFAULT, ji.getPriority());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
+
+ ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setPriority(JobInfo.PRIORITY_LOW)
+ .build();
+ assertEquals(JobInfo.PRIORITY_LOW, ji.getPriority());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
+
+ ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setPriority(JobInfo.PRIORITY_MIN)
+ .build();
+ assertEquals(JobInfo.PRIORITY_MIN, ji.getPriority());
+ // Confirm JobScheduler accepts the JobInfo object.
+ mJobScheduler.schedule(ji);
+
+ // Attempt an invalid number
+ try {
+ // It's over 9000!!!
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent).setPriority(9001).build();
+ fail("Successfully built a job with an invalid priority level");
+ } catch (Exception e) {
+ // Success
+ }
+ try {
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent).setPriority(-1).build();
+ fail("Successfully built a job with an invalid priority level");
+ } catch (Exception e) {
+ // Success
+ }
+ try {
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent).setPriority(123).build();
+ fail("Successfully built a job with an invalid priority level");
+ } catch (Exception e) {
+ // Success
+ }
+
+ // Test other invalid configurations.
+ final String failureMessage =
+ "Successfully built a JobInfo object with disallowed priority configurations";
+ assertBuildFails(failureMessage,
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setPriority(JobInfo.PRIORITY_MAX));
+ //noinspection deprecation
+ assertBuildFails(failureMessage,
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setPriority(JobInfo.PRIORITY_LOW)
+ .setImportantWhileForeground(true));
+ assertBuildFails(failureMessage,
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setPriority(JobInfo.PRIORITY_HIGH)
+ .setPrefetch(true));
+ assertBuildFails(failureMessage,
+ new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setPriority(JobInfo.PRIORITY_HIGH)
+ .setPeriodic(JobInfo.getMinPeriodMillis()));
}
public void testRequiredNetwork() {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobSchedulingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobSchedulingTest.java
index 05d78de..830486e 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobSchedulingTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobSchedulingTest.java
@@ -19,10 +19,15 @@
import android.annotation.TargetApi;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
+import android.jobscheduler.MockJobService.TestEnvironment;
+import android.jobscheduler.MockJobService.TestEnvironment.Event;
import android.provider.DeviceConfig;
+import com.android.compatibility.common.util.BatteryUtils;
import com.android.compatibility.common.util.SystemUtil;
+import java.util.List;
+
/**
* Tests related to scheduling jobs.
*/
@@ -30,11 +35,15 @@
public class JobSchedulingTest extends BaseJobSchedulerTest {
private static final int MIN_SCHEDULE_QUOTA = 250;
private static final int JOB_ID = JobSchedulingTest.class.hashCode();
+ // The maximum number of jobs that can run concurrently.
+ private static final int MAX_JOB_CONTEXTS_COUNT = 16;
@Override
public void tearDown() throws Exception {
mJobScheduler.cancel(JOB_ID);
SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler reset-schedule-quota");
+ SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler monitor-battery off");
+ BatteryUtils.runDumpsysBatteryReset();
// The super method should be called at the end.
super.tearDown();
@@ -127,4 +136,45 @@
assertEquals(JobScheduler.RESULT_SUCCESS, mJobScheduler.schedule(jobInfo));
}
}
+
+ public void testHigherPriorityJobRunsFirst() throws Exception {
+ SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler monitor-battery on");
+
+ setBatteryState(false, 75);
+ final int higherPriorityJobId = JOB_ID;
+ final int numMinPriorityJobs = 2 * MAX_JOB_CONTEXTS_COUNT;
+ kTestEnvironment.setExpectedExecutions(1 + numMinPriorityJobs);
+ for (int i = 0; i < numMinPriorityJobs; ++i) {
+ JobInfo job = new JobInfo.Builder(higherPriorityJobId + 1 + i, kJobServiceComponent)
+ .setPriority(JobInfo.PRIORITY_MIN)
+ .setRequiresCharging(true)
+ .build();
+ mJobScheduler.schedule(job);
+ }
+ // Schedule the higher priority job last since the default sorting is by enqueue time.
+ JobInfo jobMax = new JobInfo.Builder(higherPriorityJobId, kJobServiceComponent)
+ .setPriority(JobInfo.PRIORITY_DEFAULT)
+ .setRequiresCharging(true)
+ .build();
+ mJobScheduler.schedule(jobMax);
+
+ setBatteryState(true, 100);
+ kTestEnvironment.awaitExecution();
+
+ Event jobHigherExecution = new Event(TestEnvironment.Event.EVENT_START_JOB,
+ higherPriorityJobId);
+ List<Event> executedEvents = kTestEnvironment.getExecutedEvents();
+ boolean higherExecutedFirst = false;
+ // Due to racing, we can't just check the very first item in the array. We can however
+ // make sure it was in the first set of jobs to run.
+ for (int i = 0; i < executedEvents.size() && i < MAX_JOB_CONTEXTS_COUNT; ++i) {
+ if (executedEvents.get(i).equals(jobHigherExecution)) {
+ higherExecutedFirst = true;
+ break;
+ }
+ }
+ assertTrue(
+ "Higher priority job (" + higherPriorityJobId + ") didn't run in first batch: "
+ + executedEvents, higherExecutedFirst);
+ }
}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
index 8ab63ca..de4bc7c 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
@@ -23,7 +23,6 @@
import static android.jobscheduler.cts.ConnectivityConstraintTest.setWifiState;
import static android.jobscheduler.cts.TestAppInterface.TEST_APP_PACKAGE;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
-import static android.os.PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED;
import static com.android.compatibility.common.util.TestUtils.waitUntil;
@@ -133,7 +132,6 @@
Log.d(TAG, "Received action " + intent.getAction());
switch (intent.getAction()) {
case ACTION_DEVICE_IDLE_MODE_CHANGED:
- case ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
synchronized (JobThrottlingTest.this) {
mDeviceInDoze = mPowerManager.isDeviceIdleMode();
Log.d(TAG, "mDeviceInDoze: " + mDeviceInDoze);
@@ -159,7 +157,6 @@
mTestAppInterface = new TestAppInterface(mContext, mTestJobId);
final IntentFilter intentFilter = new IntentFilter();
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();
@@ -197,7 +194,7 @@
if (mAutomotiveDevice || mLeanbackOnly) {
setScreenState(true);
// TODO(b/159176758): make sure that initial power supply is on.
- BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+ setChargingState(true);
}
// Kill as many things in the background as possible so we avoid LMK interfering with the
@@ -354,31 +351,97 @@
mTestAppInterface.getLastParams().getStopReason());
}
- @RequiresDevice // Emulators don't always have access to wifi/network
@Test
- public void testBackgroundConnectivityJobsThrottled() throws Exception {
- if (!mHasWifi) {
- Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
- return;
- }
- ensureSavedWifiNetwork(mWifiManager);
- setAirplaneMode(false);
- setWifiState(true, mCm, mWifiManager);
- assumeTrue("device idle not enabled", mDeviceIdleEnabled);
- mTestAppInterface.scheduleJob(false, NETWORK_TYPE_ANY, false);
+ public void testBackgroundRegJobsThermal() throws Exception {
+ mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, false);
runJob();
assertTrue("Job did not start after scheduling",
mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
- ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_CRITICAL);
+
+ ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_LIGHT);
+ assertFalse("Job stopped below thermal throttling threshold",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+
+ ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_SEVERE);
assertTrue("Job did not stop on thermal throttling",
mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
- Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
+ final long jobStopTime = System.currentTimeMillis();
+
+ ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_CRITICAL);
+ runJob();
+ assertFalse("Job started above thermal throttling threshold",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+ ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_EMERGENCY);
+ runJob();
+ assertFalse("Job started above thermal throttling threshold",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+ Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF
+ - (System.currentTimeMillis() - jobStopTime));
ThermalUtils.overrideThermalNotThrottling();
runJob();
assertTrue("Job did not start back from throttling",
mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
}
+ @Test
+ public void testBackgroundEJsThermal() throws Exception {
+ mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, true);
+ runJob();
+ assertTrue("Job did not start after scheduling",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+ ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_MODERATE);
+ assertFalse("Job stopped below thermal throttling threshold",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+
+ ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_SEVERE);
+ assertTrue("Job did not stop on thermal throttling",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ final long jobStopTime = System.currentTimeMillis();
+
+ ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_CRITICAL);
+ runJob();
+ assertFalse("Job started above thermal throttling threshold",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+ ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_EMERGENCY);
+ runJob();
+ assertFalse("Job started above thermal throttling threshold",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+ Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF
+ - (System.currentTimeMillis() - jobStopTime));
+ ThermalUtils.overrideThermalNotThrottling();
+ runJob();
+ assertTrue("Job did not start back from throttling",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ }
+
+ @Test
+ public void testForegroundJobsThermal() throws Exception {
+ // Turn the screen on to ensure the app gets into the TOP state.
+ setScreenState(true);
+ mTestAppInterface.startAndKeepTestActivity(true);
+ mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, false);
+ runJob();
+ assertTrue("Job did not start after scheduling",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+ ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_MODERATE);
+ assertFalse("Job stopped below thermal throttling threshold",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+
+ ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_SEVERE);
+ assertFalse("Job stopped despite being TOP app",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+
+ ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_CRITICAL);
+ assertFalse("Job stopped despite being TOP app",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ }
+
/** Tests that apps in the RESTRICTED bucket still get their one parole session per day. */
@Test
public void testJobsInRestrictedBucket_ParoleSession() throws Exception {
@@ -393,7 +456,7 @@
setScreenState(true);
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
setTestPackageStandbyBucket(Bucket.RESTRICTED);
Thread.sleep(DEFAULT_WAIT_TIMEOUT);
sendScheduleJobBroadcast(false);
@@ -422,7 +485,7 @@
mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "1");
setScreenState(true);
- BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+ setChargingState(true);
BatteryUtils.runDumpsysBatterySetLevel(100);
setTestPackageStandbyBucket(Bucket.RESTRICTED);
@@ -454,7 +517,7 @@
setScreenState(true);
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
setTestPackageStandbyBucket(Bucket.RESTRICTED);
Thread.sleep(DEFAULT_WAIT_TIMEOUT);
sendScheduleJobBroadcast(false);
@@ -466,7 +529,7 @@
assertFalse("New job started in RESTRICTED bucket after parole used",
mTestAppInterface.awaitJobStart(3_000));
- BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+ setChargingState(true);
BatteryUtils.runDumpsysBatterySetLevel(100);
assertFalse("New job started in RESTRICTED bucket after parole when charging but not idle",
mTestAppInterface.awaitJobStart(3_000));
@@ -478,6 +541,7 @@
// Make sure job can be stopped and started again when charging + idle
sendScheduleJobBroadcast(false);
+ runJob();
assertTrue("Job didn't restart in RESTRICTED bucket when charging + idle",
mTestAppInterface.awaitJobStart(3_000));
}
@@ -497,7 +561,7 @@
setAirplaneMode(true);
setScreenState(true);
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
setTestPackageStandbyBucket(Bucket.RESTRICTED);
Thread.sleep(DEFAULT_WAIT_TIMEOUT);
mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, false);
@@ -506,7 +570,7 @@
// Slowly add back required bucket constraints.
// Battery charging and high.
- BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+ setChargingState(true);
assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
BatteryUtils.runDumpsysBatterySetLevel(100);
assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
@@ -538,7 +602,7 @@
setAirplaneMode(true);
setScreenState(true);
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
setTestPackageStandbyBucket(Bucket.RESTRICTED);
Thread.sleep(DEFAULT_WAIT_TIMEOUT);
mTestAppInterface.scheduleJob(false, NETWORK_TYPE_ANY, false);
@@ -548,7 +612,7 @@
// Slowly add back required bucket constraints.
// Battery charging and high.
- BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+ setChargingState(true);
runJob();
assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
BatteryUtils.runDumpsysBatterySetLevel(100);
@@ -578,7 +642,7 @@
assumeFalse("not testable in automotive device", mAutomotiveDevice);
assumeFalse("not testable in leanback device", mLeanbackOnly);
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
setTestPackageStandbyBucket(Bucket.NEVER);
Thread.sleep(DEFAULT_WAIT_TIMEOUT);
sendScheduleJobBroadcast(false);
@@ -590,7 +654,7 @@
assumeFalse("not testable in automotive device", mAutomotiveDevice);
assumeFalse("not testable in leanback device", mLeanbackOnly);
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
setTestPackageStandbyBucket(Bucket.NEVER);
tempWhitelistTestApp(6_000);
Thread.sleep(DEFAULT_WAIT_TIMEOUT);
@@ -606,7 +670,7 @@
BatteryUtils.assumeBatterySaverFeature();
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
BatteryUtils.enableBatterySaver(false);
sendScheduleJobBroadcast(false);
assertTrue("New job failed to start with battery saver OFF",
@@ -620,7 +684,7 @@
BatteryUtils.assumeBatterySaverFeature();
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
BatteryUtils.enableBatterySaver(true);
sendScheduleJobBroadcast(false);
assertFalse("New job started with battery saver ON",
@@ -634,7 +698,7 @@
BatteryUtils.assumeBatterySaverFeature();
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
BatteryUtils.enableBatterySaver(true);
tempWhitelistTestApp(6_000);
sendScheduleJobBroadcast(false);
@@ -650,7 +714,7 @@
BatteryUtils.assumeBatterySaverFeature();
// Enable battery saver, and schedule a job. It shouldn't run.
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
BatteryUtils.enableBatterySaver(true);
sendScheduleJobBroadcast(false);
assertFalse("New job started with battery saver ON",
@@ -669,7 +733,7 @@
BatteryUtils.assumeBatterySaverFeature();
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
BatteryUtils.enableBatterySaver(true);
mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
assertTrue("New expedited job failed to start with battery saver ON",
@@ -683,7 +747,7 @@
BatteryUtils.assumeBatterySaverFeature();
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
BatteryUtils.enableBatterySaver(false);
mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
assertTrue("New expedited job failed to start with battery saver ON",
@@ -756,7 +820,7 @@
// Intentionally set a value below 1 minute to ensure the range checks work.
mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(47_000L));
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
BatteryUtils.enableBatterySaver(true);
mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
runJob();
@@ -788,7 +852,7 @@
assumeTrue("device idle not enabled", mDeviceIdleEnabled);
mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(60_000L));
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
toggleDozeState(true);
mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
runJob();
@@ -846,7 +910,7 @@
// Intentionally set a value below 1 minute to ensure the range checks work.
mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(0L));
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
BatteryUtils.enableBatterySaver(false);
mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
runJob();
@@ -883,7 +947,7 @@
setAirplaneMode(false);
setWifiState(true, mCm, mWifiManager);
setWifiMeteredState(false);
- BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+ setChargingState(true);
BatteryUtils.runDumpsysBatterySetLevel(100);
setScreenState(false);
triggerJobIdle();
@@ -921,12 +985,12 @@
runJob();
assertTrue("New job didn't start in RESTRICTED bucket",
mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
assertTrue("New job didn't stop when device no longer charging",
mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
assertEquals(JobParameters.STOP_REASON_APP_STANDBY,
mTestAppInterface.getLastParams().getStopReason());
- BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+ setChargingState(true);
BatteryUtils.runDumpsysBatterySetLevel(100);
// Battery not low
@@ -947,7 +1011,7 @@
public void testRestrictingStopReason_Quota() throws Exception {
// Reduce allowed time for testing.
mDeviceConfigStateHelper.set("qc_allowed_time_per_period_ms", "60000");
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
setTestPackageStandbyBucket(Bucket.RARE);
sendScheduleJobBroadcast(false);
@@ -967,7 +1031,7 @@
public void testRestrictingStopReason_BatterySaver() throws Exception {
BatteryUtils.assumeBatterySaverFeature();
- BatteryUtils.runDumpsysBatteryUnplug();
+ setChargingState(false);
BatteryUtils.enableBatterySaver(false);
sendScheduleJobBroadcast(false);
runJob();
@@ -1007,6 +1071,7 @@
toggleDozeState(false);
}
mTestAppInterface.cleanup();
+ mUiDevice.executeShellCommand("cmd jobscheduler monitor-battery off");
BatteryUtils.runDumpsysBatteryReset();
BatteryUtils.enableBatterySaver(false);
removeTestAppFromTempWhitelist();
@@ -1132,6 +1197,33 @@
Thread.sleep(2_000);
}
+ private void setChargingState(boolean isCharging) throws Exception {
+ mUiDevice.executeShellCommand("cmd jobscheduler monitor-battery on");
+
+ final String command;
+ if (isCharging) {
+ mUiDevice.executeShellCommand("cmd battery set ac 1");
+ final int curLevel = Integer.parseInt(
+ mUiDevice.executeShellCommand("dumpsys battery get level").trim());
+ command = "cmd battery set -f level " + Math.min(100, curLevel + 1);
+ } else {
+ command = "cmd battery unplug -f";
+ }
+ int seq = Integer.parseInt(mUiDevice.executeShellCommand(command).trim());
+
+ // Wait for the battery update to be processed by job scheduler before proceeding.
+ waitUntil("JobScheduler didn't update charging status to " + isCharging, 15 /* seconds */,
+ () -> {
+ int curSeq;
+ boolean curCharging;
+ curSeq = Integer.parseInt(mUiDevice.executeShellCommand(
+ "cmd jobscheduler get-battery-seq").trim());
+ curCharging = Boolean.parseBoolean(mUiDevice.executeShellCommand(
+ "cmd jobscheduler get-battery-charging").trim());
+ return curSeq >= seq && curCharging == isCharging;
+ });
+ }
+
/**
* Trigger job idle (not device idle);
*/
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobWorkItemTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobWorkItemTest.java
index e05969e..457ec1f 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobWorkItemTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobWorkItemTest.java
@@ -50,6 +50,48 @@
assertEquals(0, jwi.getDeliveryCount());
}
+ public void testItemWithMinimumChunkBytes() {
+ JobWorkItem jwi = new JobWorkItem(TEST_INTENT, 10, 20, 3);
+
+ assertEquals(TEST_INTENT, jwi.getIntent());
+ assertEquals(10, jwi.getEstimatedNetworkDownloadBytes());
+ assertEquals(20, jwi.getEstimatedNetworkUploadBytes());
+ assertEquals(3, jwi.getMinimumNetworkChunkBytes());
+ // JobWorkItem hasn't been scheduled yet. Delivery count should be 0.
+ assertEquals(0, jwi.getDeliveryCount());
+
+ try {
+ new JobWorkItem(TEST_INTENT, 10, 20, -3);
+ fail("Successfully created JobWorkItem with negative minimum chunk value");
+ } catch (IllegalArgumentException expected) {
+ // Success
+ }
+ try {
+ new JobWorkItem(TEST_INTENT, 10, 20, 0);
+ fail("Successfully created JobWorkItem with 0 minimum chunk value");
+ } catch (IllegalArgumentException expected) {
+ // Success
+ }
+ try {
+ new JobWorkItem(TEST_INTENT, 10, 20, 50);
+ fail("Successfully created JobWorkItem with minimum chunk value too large");
+ } catch (IllegalArgumentException expected) {
+ // Success
+ }
+ try {
+ new JobWorkItem(TEST_INTENT, JobInfo.NETWORK_BYTES_UNKNOWN, 20, 25);
+ fail("Successfully created JobWorkItem with minimum chunk value too large");
+ } catch (IllegalArgumentException expected) {
+ // Success
+ }
+ try {
+ new JobWorkItem(TEST_INTENT, 10, JobInfo.NETWORK_BYTES_UNKNOWN, 15);
+ fail("Successfully created JobWorkItem with minimum chunk value too large");
+ } catch (IllegalArgumentException expected) {
+ // Success
+ }
+ }
+
public void testDeliveryCountBumped() throws Exception {
JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.setOverrideDeadline(0)
diff --git a/tests/MediaProviderTranscode/AndroidTest.xml b/tests/MediaProviderTranscode/AndroidTest.xml
index 8dba741..7dc78eb 100644
--- a/tests/MediaProviderTranscode/AndroidTest.xml
+++ b/tests/MediaProviderTranscode/AndroidTest.xml
@@ -19,6 +19,11 @@
<option name="install-arg" value="-g" />
</target_preparer>
+ <option
+ name="config-descriptor:metadata"
+ key="mainline-param"
+ value="com.google.android.mediaprovider.apex" />
+
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="cts" />
<option name="test-tag" value="MediaProviderTranscodeTests" />
diff --git a/tests/PhotoPicker/Android.bp b/tests/PhotoPicker/Android.bp
new file mode 100644
index 0000000..37b049b
--- /dev/null
+++ b/tests/PhotoPicker/Android.bp
@@ -0,0 +1,40 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsPhotoPickerTest",
+ manifest: "AndroidManifest.xml",
+ test_config: "AndroidTest.xml",
+ srcs: ["src/**/*.java", "helper/**/*.java", ":CtsProviderTestUtils",],
+ compile_multilib: "both",
+ test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+ sdk_version: "core_current",
+ min_sdk_version: "30",
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ "framework-mediaprovider.impl",
+ "framework-res",
+ "android_test_stubs_current"],
+ static_libs: [
+ "androidx.test.rules",
+ "cts-install-lib",
+ "androidx.test.uiautomator_uiautomator",
+ "Harrier",
+ ],
+}
diff --git a/tests/PhotoPicker/AndroidManifest.xml b/tests/PhotoPicker/AndroidManifest.xml
new file mode 100644
index 0000000..ea26ad1
--- /dev/null
+++ b/tests/PhotoPicker/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.photopicker.cts">
+<application android:label="Photo Picker Device Tests">
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.photopicker.cts.GetResultActivity" />
+</application>
+
+<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.photopicker.cts"
+ android:label="Device-only photo picker tests" />
+
+</manifest>
diff --git a/tests/PhotoPicker/AndroidTest.xml b/tests/PhotoPicker/AndroidTest.xml
new file mode 100644
index 0000000..dbd1bc4
--- /dev/null
+++ b/tests/PhotoPicker/AndroidTest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="Runs device-only tests for photo picker">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsPhotoPickerTest.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ <option name="force-root" value="false" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="cts" />
+ <option name="test-tag" value="PhotoPickerTests" />
+ <!-- Instant apps cannot access external storage -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.photopicker.cts" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
+ <option name="instrumentation-arg" key="thisisignored" value="thisisignored --no-window-animation" />
+ </test>
+ <option name="config-descriptor:metadata" key="parameter" value="multiuser" />
+
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
+ </object>
+</configuration>
diff --git a/tests/PhotoPicker/OWNERS b/tests/PhotoPicker/OWNERS
new file mode 100644
index 0000000..79add9e
--- /dev/null
+++ b/tests/PhotoPicker/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 95221
+
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/tests/PhotoPicker/TEST_MAPPING b/tests/PhotoPicker/TEST_MAPPING
new file mode 100644
index 0000000..f48e90c
--- /dev/null
+++ b/tests/PhotoPicker/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPhotoPickerTest"
+ }
+ ]
+}
diff --git a/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg b/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg
new file mode 100644
index 0000000..d264196
--- /dev/null
+++ b/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg
Binary files differ
diff --git a/tests/PhotoPicker/res/raw/testvideo_meta.mp4 b/tests/PhotoPicker/res/raw/testvideo_meta.mp4
new file mode 100644
index 0000000..9b38f0e
--- /dev/null
+++ b/tests/PhotoPicker/res/raw/testvideo_meta.mp4
Binary files differ
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java b/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java
new file mode 100644
index 0000000..35e7830
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.photopicker.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GetResultActivity extends Activity {
+ private static LinkedBlockingQueue<Result> sResult;
+
+ public static class Result {
+ public final int requestCode;
+ public final int resultCode;
+ public final Intent data;
+
+ public Result(int requestCode, int resultCode, Intent data) {
+ this.requestCode = requestCode;
+ this.resultCode = resultCode;
+ this.data = data;
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ try {
+ sResult.offer(new Result(requestCode, resultCode, data), 5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void clearResult() {
+ sResult = new LinkedBlockingQueue<>();
+ }
+
+ public Result getResult() {
+ final Result result;
+ try {
+ result = sResult.poll(40, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ if (result == null) {
+ throw new IllegalStateException("Activity didn't receive a Result in 40 seconds");
+ }
+ return result;
+ }
+
+ public Result getResult(long timeout, TimeUnit unit) {
+ try {
+ return sResult.poll(timeout, unit);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java
new file mode 100644
index 0000000..49a1acb
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.photopicker.cts;
+
+import android.Manifest;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.DeviceConfig;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
+
+import org.junit.Before;
+
+/**
+ * Photo Picker Base class for Photo Picker tests. This includes common setup methods
+ * required for all Photo Picker tests.
+ */
+public class PhotoPickerBaseTest {
+ public static int REQUEST_CODE = 42;
+
+ protected GetResultActivity mActivity;
+ protected Context mContext;
+ protected UiDevice mDevice;
+
+ @Before
+ public void setUp() throws Exception {
+ final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+
+ enablePhotoPickerFlag(inst);
+
+ mContext = inst.getContext();
+ final Intent intent = new Intent(mContext, GetResultActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Wake up the device and dismiss the keyguard before the test starts
+ mDevice = UiDevice.getInstance(inst);
+ mDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ mDevice.executeShellCommand("wm dismiss-keyguard");
+
+ mActivity = (GetResultActivity) inst.startActivitySync(intent);
+ // Wait for the UI Thread to become idle.
+ inst.waitForIdleSync();
+ mActivity.clearResult();
+ mDevice.waitForIdle();
+ }
+
+ private void enablePhotoPickerFlag(Instrumentation inst) {
+ inst.getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.WRITE_DEVICE_CONFIG);
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ "picker_intent_enabled" /* name */,
+ "true" /* value */,
+ false /* makeDefault */);
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java
new file mode 100644
index 0000000..53810f7
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.photopicker.cts;
+
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPickerUriFormat;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImages;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findProfileButton;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ClipData;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.uiautomator.UiObject;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
+
+import org.junit.After;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Photo Picker Device only tests for cross profile interaction flows.
+ */
+@RunWith(BedsteadJUnit4.class)
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
+public class PhotoPickerCrossProfileTest extends PhotoPickerBaseTest {
+ @ClassRule @Rule
+ public static final DeviceState sDeviceState = new DeviceState();
+
+ private List<Uri> mUriList = new ArrayList<>();
+
+ @After
+ public void tearDown() throws Exception {
+ for (Uri uri : mUriList) {
+ deleteMedia(uri, mContext.getUserId());
+ }
+ mActivity.finish();
+ }
+
+ @Test
+ @RequireRunOnWorkProfile
+ public void testWorkApp_canAccessPersonalProfileContents() throws Exception {
+ final int imageCount = 2;
+ createImages(imageCount, sDeviceState.primaryUser().id(), mUriList);
+
+ Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, imageCount);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ // Click the profile button to change to personal profile
+ final UiObject profileButton = findProfileButton();
+ profileButton.click();
+ mDevice.waitForIdle();
+
+ final List<UiObject> itemList = findItemList(imageCount);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isEqualTo(imageCount);
+ for (int i = 0; i < itemCount; i++) {
+ final UiObject item = itemList.get(i);
+ item.click();
+ mDevice.waitForIdle();
+ }
+
+ final UiObject addButton = findAddButton();
+ addButton.click();
+ mDevice.waitForIdle();
+
+ final ClipData clipData = mActivity.getResult().data.getClipData();
+ final int count = clipData.getItemCount();
+ assertThat(count).isEqualTo(imageCount);
+ for (int i = 0; i < count; i++) {
+ Uri uri = clipData.getItemAt(i).getUri();
+ assertPickerUriFormat(uri, sDeviceState.primaryUser().id());
+ assertRedactedReadOnlyAccess(uri);
+ }
+ }
+
+ @Test
+ @EnsureHasWorkProfile
+ public void testPersonalApp_canAccessWorkProfileContents() throws Exception {
+ final int imageCount = 2;
+ createImages(imageCount, sDeviceState.workProfile().id(), mUriList);
+
+ Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, imageCount);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ // Click the profile button to change to work profile
+ final UiObject profileButton = findProfileButton();
+ profileButton.click();
+ mDevice.waitForIdle();
+
+ final List<UiObject> itemList = findItemList(imageCount);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isEqualTo(imageCount);
+ for (int i = 0; i < itemCount; i++) {
+ final UiObject item = itemList.get(i);
+ item.click();
+ mDevice.waitForIdle();
+ }
+
+ final UiObject addButton = findAddButton();
+ addButton.click();
+ mDevice.waitForIdle();
+
+ final ClipData clipData = mActivity.getResult().data.getClipData();
+ final int count = clipData.getItemCount();
+ assertThat(count).isEqualTo(imageCount);
+ for (int i = 0; i < count; i++) {
+ Uri uri = clipData.getItemAt(i).getUri();
+ assertPickerUriFormat(uri, sDeviceState.workProfile().id());
+ assertRedactedReadOnlyAccess(uri);
+ }
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java
new file mode 100644
index 0000000..9b5904c
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.photopicker.cts;
+
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertMimeType;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPickerUriFormat;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImages;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createVideos;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.REGEX_PACKAGE_NAME;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddButton;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddOrSelectButton;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiSelector;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Photo Picker Device only tests for common flows.
+ */
+@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
+public class PhotoPickerTest extends PhotoPickerBaseTest {
+ private List<Uri> mUriList = new ArrayList<>();
+
+ @After
+ public void tearDown() throws Exception {
+ for (Uri uri : mUriList) {
+ deleteMedia(uri, mContext.getUserId());
+ }
+ mActivity.finish();
+ }
+
+ @Test
+ public void testSingleSelect() throws Exception {
+ final int itemCount = 1;
+ createImages(itemCount, mContext.getUserId(), mUriList);
+
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final UiObject item = findItemList(itemCount).get(0);
+ item.click();
+ mDevice.waitForIdle();
+
+ final Uri uri = mActivity.getResult().data.getData();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertRedactedReadOnlyAccess(uri);
+ }
+
+ @Test
+ public void testSingleSelectWithPreview() throws Exception {
+ final int itemCount = 1;
+ createImages(itemCount, mContext.getUserId(), mUriList);
+
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final UiObject item = findItemList(itemCount).get(0);
+ item.longClick();
+ mDevice.waitForIdle();
+
+ final UiObject addButton = findPreviewAddOrSelectButton();
+ addButton.click();
+ mDevice.waitForIdle();
+
+ final Uri uri = mActivity.getResult().data.getData();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertRedactedReadOnlyAccess(uri);
+ }
+
+ @Test
+ public void testMultiSelect_invalidParam() throws Exception {
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ // TODO(b/205291616): Replace 101 with MediaStore.getPickImagesMaxLimit() + 1
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 101);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+ final GetResultActivity.Result res = mActivity.getResult();
+ assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ public void testMultiSelect_invalidNegativeParam() throws Exception {
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, -1);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+ final GetResultActivity.Result res = mActivity.getResult();
+ assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ public void testMultiSelect_returnsNotMoreThanMax() throws Exception {
+ final int maxCount = 2;
+ final int imageCount = maxCount + 1;
+ createImages(imageCount, mContext.getUserId(), mUriList);
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxCount);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final List<UiObject> itemList = findItemList(imageCount);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isEqualTo(imageCount);
+ // Select maxCount + 1 item
+ for (int i = 0; i < itemCount; i++) {
+ final UiObject item = itemList.get(i);
+ item.click();
+ mDevice.waitForIdle();
+ }
+
+ UiObject snackbarTextView = mDevice.findObject(new UiSelector().text(
+ "Select up to 2 items"));
+ assertWithMessage("Timed out while waiting for snackbar to appear").that(
+ snackbarTextView.waitForExists(SHORT_TIMEOUT)).isTrue();
+
+ assertWithMessage("Timed out waiting for snackbar to disappear").that(
+ snackbarTextView.waitUntilGone(SHORT_TIMEOUT)).isTrue();
+
+ final UiObject addButton = findAddButton();
+ addButton.click();
+ mDevice.waitForIdle();
+
+ final ClipData clipData = mActivity.getResult().data.getClipData();
+ final int count = clipData.getItemCount();
+ assertThat(count).isEqualTo(maxCount);
+ }
+
+ @Test
+ public void testDoesNotRespectExtraAllowMultiple() throws Exception {
+ final int imageCount = 2;
+ createImages(imageCount, mContext.getUserId(), mUriList);
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final List<UiObject> itemList = findItemList(imageCount);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isEqualTo(imageCount);
+ // Select 1 item
+ final UiObject item = itemList.get(0);
+ item.click();
+ mDevice.waitForIdle();
+
+ final Uri uri = mActivity.getResult().data.getData();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertRedactedReadOnlyAccess(uri);
+ }
+
+ @Test
+ public void testMultiSelect() throws Exception {
+ final int imageCount = 4;
+ createImages(imageCount, mContext.getUserId(), mUriList);
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ // TODO(b/205291616): Replace 100 with MediaStore.getPickImagesMaxLimit()
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 100);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final List<UiObject> itemList = findItemList(imageCount);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isEqualTo(imageCount);
+ for (int i = 0; i < itemCount; i++) {
+ final UiObject item = itemList.get(i);
+ item.click();
+ mDevice.waitForIdle();
+ }
+
+ final UiObject addButton = findAddButton();
+ addButton.click();
+ mDevice.waitForIdle();
+
+ final ClipData clipData = mActivity.getResult().data.getClipData();
+ final int count = clipData.getItemCount();
+ assertThat(count).isEqualTo(itemCount);
+ for (int i = 0; i < count; i++) {
+ final Uri uri = clipData.getItemAt(i).getUri();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertRedactedReadOnlyAccess(uri);
+ }
+ }
+
+ @Test
+ public void testMultiSelect_longPress() throws Exception {
+ final int videoCount = 3;
+ createVideos(videoCount, mContext.getUserId(), mUriList);
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ // TODO(b/205291616): Replace 100 with MediaStore.getPickImagesMaxLimit()
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 100);
+ intent.setType("video/*");
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final List<UiObject> itemList = findItemList(videoCount);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isEqualTo(videoCount);
+
+ // Select one item from Photo grid
+ itemList.get(0).click();
+ mDevice.waitForIdle();
+
+ UiObject item = itemList.get(1);
+ item.longClick();
+ mDevice.waitForIdle();
+
+ // Select the item from Preview
+ final UiObject selectButton = findPreviewAddOrSelectButton();
+ selectButton.click();
+ mDevice.waitForIdle();
+
+ mDevice.pressBack();
+
+ // Select one more item from Photo grid
+ itemList.get(2).click();
+ mDevice.waitForIdle();
+
+ final UiObject addButton = findAddButton();
+ addButton.click();
+ mDevice.waitForIdle();
+
+ // Verify that all 3 items are returned
+ final ClipData clipData = mActivity.getResult().data.getClipData();
+ final int count = clipData.getItemCount();
+ assertThat(count).isEqualTo(3);
+ for (int i = 0; i < count; i++) {
+ final Uri uri = clipData.getItemAt(i).getUri();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertRedactedReadOnlyAccess(uri);
+ }
+ }
+
+ @Test
+ public void testMultiSelect_preview() throws Exception {
+ final int imageCount = 4;
+ createImages(imageCount, mContext.getUserId(), mUriList);
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ // TODO(b/205291616): Replace 100 with MediaStore.getPickImagesMaxLimit()
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 100);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final List<UiObject> itemList = findItemList(imageCount);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isEqualTo(imageCount);
+ for (int i = 0; i < itemCount; i++) {
+ final UiObject item = itemList.get(i);
+ item.click();
+ mDevice.waitForIdle();
+ }
+
+ final UiObject viewSelectedButton = findViewSelectedButton();
+ viewSelectedButton.click();
+ mDevice.waitForIdle();
+
+ // Swipe left three times
+ swipeLeft();
+ mDevice.waitForIdle();
+ swipeLeft();
+ mDevice.waitForIdle();
+ swipeLeft();
+ mDevice.waitForIdle();
+
+ // Deselect one item
+ final UiObject selectCheckButton = findPreviewSelectCheckButton();
+ selectCheckButton.click();
+ mDevice.waitForIdle();
+
+ final UiObject addButton = findPreviewAddButton();
+ addButton.click();
+ mDevice.waitForIdle();
+
+ final ClipData clipData = mActivity.getResult().data.getClipData();
+ final int count = clipData.getItemCount();
+ assertThat(count).isEqualTo(itemCount - 1);
+ for (int i = 0; i < count; i++) {
+ final Uri uri = clipData.getItemAt(i).getUri();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertRedactedReadOnlyAccess(uri);
+ }
+ }
+
+ @Test
+ public void testMimeTypeFilter() throws Exception {
+ final int videoCount = 2;
+ createVideos(videoCount, mContext.getUserId(), mUriList);
+ final int imageCount = 1;
+ createImages(imageCount, mContext.getUserId(), mUriList);
+ final String mimeType = "video/dng";
+
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ // TODO(b/205291616): Replace 100 with MediaStore.getPickImagesMaxLimit()
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 100);
+ intent.setType(mimeType);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ // find all items
+ final List<UiObject> itemList = findItemList(-1);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isAtLeast(videoCount);
+ for (int i = 0; i < itemCount; i++) {
+ final UiObject item = itemList.get(i);
+ item.click();
+ mDevice.waitForIdle();
+ }
+
+ final UiObject addButton = findAddButton();
+ addButton.click();
+ mDevice.waitForIdle();
+
+ final ClipData clipData = mActivity.getResult().data.getClipData();
+ final int count = clipData.getItemCount();
+ assertThat(count).isEqualTo(itemCount);
+ for (int i = 0; i < count; i++) {
+ final Uri uri = clipData.getItemAt(i).getUri();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertRedactedReadOnlyAccess(uri);
+ assertMimeType(uri, mimeType);
+ }
+ }
+
+ private static UiObject findViewSelectedButton() {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/button_view_selected"));
+ }
+
+ private static UiObject findPreviewSelectCheckButton() {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/preview_select_check_button"));
+ }
+
+ private void swipeLeft() {
+ final int width = mDevice.getDisplayWidth();
+ final int height = mDevice.getDisplayHeight();
+ mDevice.swipe(width / 2, height / 2, width / 4, height / 2, 10);
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java
new file mode 100644
index 0000000..4a8f823
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.photopicker.cts.util;
+
+import static android.os.SystemProperties.getBoolean;
+import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE;
+import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO;
+import static android.provider.MediaStore.PickerMediaColumns;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * Photo Picker Utility methods for test assertions.
+ */
+public class PhotoPickerAssertionsUtils {
+ private static final String TAG = "PhotoPickerTestAssertions";
+
+ public static void assertPickerUriFormat(Uri uri, int expectedUserId) {
+ // content://media/picker/<user-id>/<media-id>
+ final int userId = Integer.parseInt(uri.getPathSegments().get(1));
+ assertThat(userId).isEqualTo(expectedUserId);
+
+ final String auth = uri.getPathSegments().get(0);
+ assertThat(auth).isEqualTo("picker");
+ }
+
+ public static void assertMimeType(Uri uri, String expectedMimeType) throws Exception {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ final String resultMimeType = context.getContentResolver().getType(uri);
+ assertThat(resultMimeType).isEqualTo(expectedMimeType);
+ }
+
+ public static void assertRedactedReadOnlyAccess(Uri uri) throws Exception {
+ assertThat(uri).isNotNull();
+ final String[] projection = new String[]{ PickerMediaColumns.MIME_TYPE };
+ final Context context = InstrumentationRegistry.getTargetContext();
+ final ContentResolver resolver = context.getContentResolver();
+ try (Cursor c = resolver.query(uri, projection, null, null)) {
+ assertThat(c).isNotNull();
+ assertThat(c.moveToFirst()).isTrue();
+
+ if (getBoolean("sys.photopicker.pickerdb.enabled", true)) {
+ final String mimeType = c.getString(c.getColumnIndex(
+ PickerMediaColumns.MIME_TYPE));
+ if (mimeType.startsWith("image")) {
+ assertImageRedactedReadOnlyAccess(uri, resolver);
+ } else if (mimeType.startsWith("video")) {
+ assertVideoRedactedReadOnlyAccess(uri, resolver);
+ } else {
+ fail("The mime type is not as expected: " + mimeType);
+ }
+ } else {
+ final int mediaType = c.getInt(1);
+ switch (mediaType) {
+ case MEDIA_TYPE_IMAGE:
+ assertImageRedactedReadOnlyAccess(uri, resolver);
+ break;
+ case MEDIA_TYPE_VIDEO:
+ assertVideoRedactedReadOnlyAccess(uri, resolver);
+ break;
+ default:
+ fail("The media type is not as expected: " + mediaType);
+ }
+ }
+ }
+ }
+
+ private static void assertVideoRedactedReadOnlyAccess(Uri uri, ContentResolver resolver)
+ throws Exception {
+ // The location is redacted
+ try (InputStream in = resolver.openInputStream(uri);
+ ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ FileUtils.copy(in, out);
+ byte[] bytes = out.toByteArray();
+ byte[] xmpBytes = Arrays.copyOfRange(bytes, 3269, 3269 + 13197);
+ String xmp = new String(xmpBytes);
+ assertWithMessage("Failed to redact XMP longitude")
+ .that(xmp.contains("10,41.751000E")).isFalse();
+ assertWithMessage("Failed to redact XMP latitude")
+ .that(xmp.contains("53,50.070500N")).isFalse();
+ assertWithMessage("Redacted non-location XMP")
+ .that(xmp.contains("13166/7763")).isTrue();
+ }
+
+ // assert no write access
+ try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) {
+ fail("Does not grant write access to uri " + uri.toString());
+ } catch (SecurityException | FileNotFoundException expected) {
+ }
+ }
+
+ private static void assertImageRedactedReadOnlyAccess(Uri uri, ContentResolver resolver)
+ throws Exception {
+ // The location is redacted
+ try (InputStream is = resolver.openInputStream(uri)) {
+ final ExifInterface exif = new ExifInterface(is);
+ final float[] latLong = new float[2];
+ exif.getLatLong(latLong);
+ assertWithMessage("Failed to redact latitude")
+ .that(latLong[0]).isWithin(0.001f).of(0);
+ assertWithMessage("Failed to redact longitude")
+ .that(latLong[1]).isWithin(0.001f).of(0);
+
+ String xmp = exif.getAttribute(ExifInterface.TAG_XMP);
+ assertWithMessage("Failed to redact XMP longitude")
+ .that(xmp.contains("10,41.751000E")).isFalse();
+ assertWithMessage("Failed to redact XMP latitude")
+ .that(xmp.contains("53,50.070500N")).isFalse();
+ assertWithMessage("Redacted non-location XMP")
+ .that(xmp.contains("LensDefaults")).isTrue();
+ }
+
+ // assert no write access
+ try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) {
+ fail("Does not grant write access to uri " + uri.toString());
+ } catch (SecurityException | FileNotFoundException expected) {
+ }
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java
new file mode 100644
index 0000000..37a44f9
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.photopicker.cts.util;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.UserHandle;
+import android.photopicker.cts.R;
+import android.provider.MediaStore;
+import android.provider.cts.media.MediaStoreUtils;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+/**
+ * Photo Picker Utility methods for media files creation and deletion.
+ */
+public class PhotoPickerFilesUtils {
+ public static final String DISPLAY_NAME_PREFIX = "ctsPhotoPicker";
+
+ public static void createImages(int count, int userId, List<Uri> uriList)
+ throws Exception {
+ for (int i = 0; i < count; i++) {
+ final Uri uri = createImage(userId);
+ uriList.add(uri);
+ clearMediaOwner(uri, userId);
+ }
+ }
+
+ public static void createVideos(int count, int userId, List<Uri> uriList)
+ throws Exception {
+ for (int i = 0; i < count; i++) {
+ final Uri uri = createVideo(userId);
+ uriList.add(uri);
+ clearMediaOwner(uri, userId);
+ }
+ }
+
+ public static void deleteMedia(Uri uri, int userId) throws Exception {
+ final String cmd = String.format("content delete --uri %s --user %d", uri, userId);
+ ShellUtils.runShellCommand(cmd);
+ }
+
+ private static void clearMediaOwner(Uri uri, int userId) throws Exception {
+ final String cmd = String.format(
+ "content update --uri %s --user %d --bind owner_package_name:n:", uri, userId);
+ ShellUtils.runShellCommand(cmd);
+ }
+
+ private static Uri createVideo(int userId) throws Exception {
+ final Uri uri = stageMedia(R.raw.testvideo_meta,
+ MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId);
+ return uri;
+ }
+
+ private static Uri createImage(int userId) throws Exception {
+ final Uri uri = stageMedia(R.raw.lg_g4_iso_800_jpg,
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/jpeg", userId);
+ return uri;
+ }
+
+ private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, int userId) throws
+ Exception {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ uiAutomation.adoptShellPermissionIdentity(
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ try {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ final Context userContext = userId == context.getUserId() ? context :
+ context.createPackageContextAsUser("android", /* flags= */ 0,
+ UserHandle.of(userId));
+ return stageMedia(resId, collectionUri, mimeType, userContext);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, Context context)
+ throws IOException {
+ final String displayName = DISPLAY_NAME_PREFIX + System.nanoTime();
+ final MediaStoreUtils.PendingParams params = new MediaStoreUtils.PendingParams(
+ collectionUri, displayName, mimeType);
+ final Uri pendingUri = MediaStoreUtils.createPending(context, params);
+ try (MediaStoreUtils.PendingSession session = MediaStoreUtils.openPending(context,
+ pendingUri)) {
+ try (InputStream source = context.getResources().openRawResource(resId);
+ OutputStream target = session.openOutputStream()) {
+ FileUtils.copy(source, target);
+ }
+ return session.publish();
+ }
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java
new file mode 100644
index 0000000..d20dcd6
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.photopicker.cts.util;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.text.format.DateUtils;
+
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiScrollable;
+import androidx.test.uiautomator.UiSelector;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Photo Picker Utility methods for finding UI elements.
+ */
+public class PhotoPickerUiUtils {
+ public static final long SHORT_TIMEOUT = 5 * DateUtils.SECOND_IN_MILLIS;
+
+ private static final long TIMEOUT = 30 * DateUtils.SECOND_IN_MILLIS;
+ public static final String REGEX_PACKAGE_NAME =
+ "com(.google)?.android.providers.media(.module)?";
+
+ /**
+ * Get the list of items from the photo grid list.
+ * @param itemCount if the itemCount is -1, return all matching items. Otherwise, return the
+ * item list that its size is not greater than the itemCount.
+ * @throws Exception
+ */
+ public static List<UiObject> findItemList(int itemCount) throws Exception {
+ final List<UiObject> itemList = new ArrayList<>();
+ final UiSelector gridList = new UiSelector().className(
+ "androidx.recyclerview.widget.RecyclerView").resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/picker_tab_recyclerview");
+
+ // Wait for the first item to appear
+ assertWithMessage("Timed out while waiting for first item to appear")
+ .that(new UiObject(gridList.childSelector(new UiSelector())).waitForExists(TIMEOUT))
+ .isTrue();
+
+ final UiSelector itemSelector = new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/icon_thumbnail");
+ final UiScrollable grid = new UiScrollable(gridList);
+ final int childCount = grid.getChildCount();
+ final int count = itemCount == -1 ? childCount : itemCount;
+
+ for (int i = 0; i < childCount; i++) {
+ final UiObject item = grid.getChildByInstance(itemSelector, i);
+ if (item.exists()) {
+ itemList.add(item);
+ }
+ if (itemList.size() == count) {
+ break;
+ }
+ }
+ return itemList;
+ }
+
+ public static UiObject findPreviewAddButton() {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/preview_add_button"));
+ }
+
+ public static UiObject findPreviewAddOrSelectButton() {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/preview_add_or_select_button"));
+ }
+
+ public static UiObject findAddButton() {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/button_add"));
+ }
+
+ public static UiObject findProfileButton() {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/profile_button"));
+ }
+}
diff --git a/tests/ProcessTest/Android.bp b/tests/ProcessTest/Android.bp
deleted file mode 100644
index a0c7de5..0000000
--- a/tests/ProcessTest/Android.bp
+++ /dev/null
@@ -1,39 +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.
-
-// TODO: should it be android_helper_test_app?
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test {
- name: "ProcessTests",
- defaults: ["cts_defaults"],
- aaptflags: [
- "-c",
- "xx_YY",
- "-c",
- "cs",
- ],
- srcs: ["**/*.java"],
- static_libs: ["junit"],
- libs: ["android.test.base"],
- dex_preopt: {
- enabled: false,
- },
- optimize: {
- enabled: false,
- },
- sdk_version: "current",
-}
diff --git a/tests/ProcessTest/AndroidManifest.xml b/tests/ProcessTest/AndroidManifest.xml
deleted file mode 100644
index f2d7202..0000000
--- a/tests/ProcessTest/AndroidManifest.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?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.process"
- android:sharedUserId="com.android.cts.process.uidpid_test">
-
- <!-- InstrumentationTestRunner for AndroidTests -->
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.cts.process"
- android:label="Test process"/>
- <application>
-
- <uses-library android:name="android.test.runner" />
-
- <activity android:name=".activity.SharePidActivity"
- android:process=":shareProcess"/>
- <activity android:name=".activity.SharePidSubActivity"
- android:process=":shareProcess"/>
- <activity android:name=".activity.NoSharePidActivity"
- android:process=":noShareProcess"/>
- </application>
-
-</manifest>
diff --git a/tests/ProcessTest/NoShareUidApp/Android.bp b/tests/ProcessTest/NoShareUidApp/Android.bp
deleted file mode 100644
index e5582de..0000000
--- a/tests/ProcessTest/NoShareUidApp/Android.bp
+++ /dev/null
@@ -1,30 +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.
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
- name: "NoShareUidApp",
- defaults: ["cts_defaults"],
- srcs: ["**/*.java"],
- sdk_version: "current",
- optimize: {
- enabled: false,
- },
- dex_preopt: {
- enabled: false,
- },
-}
diff --git a/tests/ProcessTest/NoShareUidApp/AndroidManifest.xml b/tests/ProcessTest/NoShareUidApp/AndroidManifest.xml
deleted file mode 100644
index ddc73cf..0000000
--- a/tests/ProcessTest/NoShareUidApp/AndroidManifest.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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.process.noshareuidapp">
-
-</manifest>
diff --git a/tests/ProcessTest/NoShareUidApp/src/com/android/cts/process/activity/NoSharePidActivity.java b/tests/ProcessTest/NoShareUidApp/src/com/android/cts/process/activity/NoSharePidActivity.java
deleted file mode 100644
index e5f0ab4..0000000
--- a/tests/ProcessTest/NoShareUidApp/src/com/android/cts/process/activity/NoSharePidActivity.java
+++ /dev/null
@@ -1,22 +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.
- */
-package com.android.cts.process.activity;
-
-import android.app.Activity;
-
-public class NoSharePidActivity extends Activity {
-
-}
diff --git a/tests/ProcessTest/ShareUidApp/Android.bp b/tests/ProcessTest/ShareUidApp/Android.bp
deleted file mode 100644
index 9457f14..0000000
--- a/tests/ProcessTest/ShareUidApp/Android.bp
+++ /dev/null
@@ -1,30 +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.
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
- name: "ShareUidApp",
- defaults: ["cts_defaults"],
- srcs: ["**/*.java"],
- sdk_version: "current",
- optimize: {
- enabled: false,
- },
- dex_preopt: {
- enabled: false,
- },
-}
diff --git a/tests/ProcessTest/ShareUidApp/AndroidManifest.xml b/tests/ProcessTest/ShareUidApp/AndroidManifest.xml
deleted file mode 100644
index 19981f4..0000000
--- a/tests/ProcessTest/ShareUidApp/AndroidManifest.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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.process.shareuidapp"
- android:sharedUserId="com.android.cts.process.uidpid_test">
-
-</manifest>
diff --git a/tests/ProcessTest/ShareUidApp/src/com/android/cts/process/activity/SharePidActivity.java b/tests/ProcessTest/ShareUidApp/src/com/android/cts/process/activity/SharePidActivity.java
deleted file mode 100644
index 55db78b..0000000
--- a/tests/ProcessTest/ShareUidApp/src/com/android/cts/process/activity/SharePidActivity.java
+++ /dev/null
@@ -1,22 +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.
- */
-package com.android.cts.process.activity;
-
-import android.app.Activity;
-
-public class SharePidActivity extends Activity {
-
-}
diff --git a/tests/ProcessTest/ShareUidApp/src/com/android/cts/process/activity/SharePidSubActivity.java b/tests/ProcessTest/ShareUidApp/src/com/android/cts/process/activity/SharePidSubActivity.java
deleted file mode 100644
index c06d9fb..0000000
--- a/tests/ProcessTest/ShareUidApp/src/com/android/cts/process/activity/SharePidSubActivity.java
+++ /dev/null
@@ -1,22 +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.
- */
-package com.android.cts.process.activity;
-
-import android.app.Activity;
-
-public class SharePidSubActivity extends Activity {
-
-}
diff --git a/tests/ProcessTest/src/com/android/cts/process/ProcessTest.java b/tests/ProcessTest/src/com/android/cts/process/ProcessTest.java
deleted file mode 100644
index a64a900..0000000
--- a/tests/ProcessTest/src/com/android/cts/process/ProcessTest.java
+++ /dev/null
@@ -1,96 +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.
- */
-
-package com.android.cts.process;
-
-import java.util.List;
-
-import android.app.ActivityManager;
-import android.app.ActivityManager.RunningAppProcessInfo;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.test.AndroidTestCase;
-
-import com.android.cts.process.activity.NoSharePidActivity;
-import com.android.cts.process.activity.SharePidActivity;
-import com.android.cts.process.activity.SharePidSubActivity;
-
-public class ProcessTest extends AndroidTestCase {
- private final int WAIT_TIME = 2000;
-
- public void testUid() throws Exception {
- String enableApp = "com.android.cts.process.shareuidapp";
- String disableApp = "com.android.cts.process.noshareuidapp";
- String testApp = mContext.getPackageName();
- int uid1 = mContext.getPackageManager().getApplicationInfo(enableApp,
- PackageManager.GET_META_DATA).uid;
- int uid2 = mContext.getPackageManager().getApplicationInfo(disableApp,
- PackageManager.GET_META_DATA).uid;
- int uid3 = mContext.getPackageManager().getApplicationInfo(testApp,
- PackageManager.GET_META_DATA).uid;
- assertEquals(uid1, uid3);
- assertNotSame(uid2, uid3);
- }
-
- public void testPid() throws Exception {
- ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
- String shareProcessName = mContext.getPackageName() + ":shareProcess";
- String noShareProcessName = mContext.getPackageName() + ":noShareProcess";
- List<RunningAppProcessInfo> list = am.getRunningAppProcesses();
- assertEquals(-1, getPid(shareProcessName, list));
- // share pid will use same process
- Intent sharePidIntent = new Intent();
- sharePidIntent.setClass(mContext, SharePidActivity.class);
- sharePidIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(sharePidIntent);
- Thread.sleep(WAIT_TIME);
- List<RunningAppProcessInfo> sharelist = am.getRunningAppProcesses();
- int sharePid = getPid(shareProcessName, sharelist);
- assertTrue(-1 != sharePid);
- Intent sharePidStubIntent = new Intent();
- sharePidStubIntent.setClass(mContext, SharePidSubActivity.class);
- sharePidStubIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(sharePidStubIntent);
- Thread.sleep(WAIT_TIME);
- List<RunningAppProcessInfo> shareStublist = am.getRunningAppProcesses();
- int shareStubPid = getPid(shareProcessName, shareStublist);
- assertTrue(-1 != shareStubPid);
- assertEquals(sharePid, shareStubPid);
- // no share pid will create a new process
- Intent noSharePidIntent = new Intent();
- noSharePidIntent.setClass(mContext, NoSharePidActivity.class);
- noSharePidIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(noSharePidIntent);
- Thread.sleep(WAIT_TIME);
- List<RunningAppProcessInfo> noShareStublist = am.getRunningAppProcesses();
- int noSharePid = getPid(noShareProcessName, noShareStublist);
- assertTrue(-1 != noSharePid);
- assertTrue(sharePid != noSharePid);
- // kill the process after test
- android.os.Process.killProcess(noSharePid);
- android.os.Process.killProcess(sharePid);
- }
-
- private int getPid(String processName, List<RunningAppProcessInfo> list) {
- for (RunningAppProcessInfo rai : list) {
- if (processName.equals(rai.processName))
- return rai.pid;
- }
- return -1;
- }
-
-}
diff --git a/tests/accessibility/common/src/android/accessibility/cts/common/ServiceControlUtils.java b/tests/accessibility/common/src/android/accessibility/cts/common/ServiceControlUtils.java
index 067e37b..d0438bd 100644
--- a/tests/accessibility/common/src/android/accessibility/cts/common/ServiceControlUtils.java
+++ b/tests/accessibility/common/src/android/accessibility/cts/common/ServiceControlUtils.java
@@ -52,7 +52,7 @@
lock.notifyAll();
}
};
- manager.addAccessibilityServicesStateChangeListener(listener, null);
+ manager.addAccessibilityServicesStateChangeListener(listener);
try {
waitOn(lock, condition, timeoutMs, conditionName);
} finally {
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
index c083977..f86df5d 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
@@ -361,6 +361,27 @@
}
@Test
+ public void setText_unChanged_doNotReceiveEvent() throws Throwable {
+ sUiAutomation.executeAndWaitForEvent(
+ () -> sInstrumentation.runOnMainSync(() -> mTextView.setText("a")),
+ event -> isExpectedChangeType(event, AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT),
+ DEFAULT_TIMEOUT_MS);
+
+ assertThrows(
+ TimeoutException.class,
+ () ->
+ sUiAutomation.executeAndWaitForEvent(
+ () -> {
+ sInstrumentation.runOnMainSync(
+ () -> {
+ mTextView.setText("a");
+ });
+ },
+ event -> isExpectedSource(event, mTextView),
+ DEFAULT_TIMEOUT_MS));
+ }
+
+ @Test
public void setText_textChanged_receivesTextEvent() throws Throwable {
sUiAutomation.executeAndWaitForEvent(
() -> sInstrumentation.runOnMainSync(() -> mTextView.setText("a")),
@@ -610,6 +631,7 @@
sentEvent.setScrollable(true);
sentEvent.setAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
sentEvent.setMovementGranularity(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+ sentEvent.setDisplayId(Display.DEFAULT_DISPLAY);
AccessibilityRecord record = AccessibilityRecord.obtain();
AccessibilityRecordTest.fullyPopulateAccessibilityRecord(record);
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
index f1eab6c..1a359dd 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
@@ -36,14 +36,18 @@
import android.os.Handler;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
+import android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener;
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SettingsStateChangerRule;
import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.TestUtils;
import org.junit.Before;
import org.junit.Rule;
@@ -77,14 +81,6 @@
new InstrumentedAccessibilityServiceTestRule<>(
SpeakingAndVibratingAccessibilityService.class, false);
- @Rule
- public final RuleChain mRuleChain = RuleChain
- .outerRule(mSpeakingAndVibratingAccessibilityServiceRule)
- .around(mVibratingAccessibilityServiceRule)
- .around(mSpeakingAccessibilityServiceRule)
- // Inner rule capture failure and dump data before finishing activity and a11y service
- .around(mDumpOnFailureRule);
-
private static final Instrumentation sInstrumentation =
InstrumentationRegistry.getInstrumentation();
@@ -102,6 +98,23 @@
public static final String ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS =
"accessibility_interactive_ui_timeout_ms";
+ private static final String ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT =
+ "enabled_accessibility_audio_description_by_default";
+
+ private final SettingsStateChangerRule mAudioDescriptionSetterRule =
+ new SettingsStateChangerRule(
+ sInstrumentation.getContext(),
+ ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT,
+ "0");
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mSpeakingAndVibratingAccessibilityServiceRule)
+ .around(mVibratingAccessibilityServiceRule)
+ .around(mSpeakingAccessibilityServiceRule)
+ // Inner rule capture failure and dump data before finishing activity and a11y service
+ .around(mDumpOnFailureRule)
+ .around(mAudioDescriptionSetterRule);
private AccessibilityManager mAccessibilityManager;
@@ -141,6 +154,19 @@
}
@Test
+ public void testAddAndRemoveAudioDescriptionRequestedChangeListener() throws Exception {
+ AudioDescriptionRequestedChangeListener listener = (boolean enabled) -> {
+ // Do nothing.
+ };
+ mAccessibilityManager.addAudioDescriptionRequestedChangeListener(
+ mTargetContext.getMainExecutor(), listener);
+ assertTrue(
+ mAccessibilityManager.removeAudioDescriptionRequestedChangeListener(listener));
+ assertFalse(
+ mAccessibilityManager.removeAudioDescriptionRequestedChangeListener(listener));
+ }
+
+ @Test
public void testIsTouchExplorationEnabled() throws Exception {
mSpeakingAccessibilityServiceRule.enableService();
mVibratingAccessibilityServiceRule.enableService();
@@ -153,6 +179,17 @@
}
@Test
+ public void testRemoveAccessibilityServicesStateChangeListener() throws Exception {
+ AccessibilityServicesStateChangeListener listener = (state) -> {
+ /* do nothing */
+ };
+ mAccessibilityManager.addAccessibilityServicesStateChangeListener(listener);
+
+ assertTrue(mAccessibilityManager.removeAccessibilityServicesStateChangeListener(listener));
+ assertFalse(mAccessibilityManager.removeAccessibilityServicesStateChangeListener(listener));
+ }
+
+ @Test
public void testGetInstalledAccessibilityServicesList() throws Exception {
List<AccessibilityServiceInfo> installedServices =
mAccessibilityManager.getInstalledAccessibilityServiceList();
@@ -318,13 +355,13 @@
mAccessibilityManager.addTouchExplorationStateChangeListener(listener);
mSpeakingAccessibilityServiceRule.enableService();
mVibratingAccessibilityServiceRule.enableService();
- assertAtomicBooleanBecomes(atomicBoolean, true, waitObject,
- "Touch exploration state listener not called when services enabled");
+ waitForAtomicBooleanBecomes(atomicBoolean, true, waitObject,
+ "Touch exploration state listener called when services enabled");
assertTrue("Listener told that touch exploration is enabled, but manager says disabled",
mAccessibilityManager.isTouchExplorationEnabled());
InstrumentedAccessibilityService.disableAllServices();
- assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
- "Touch exploration state listener not called when services disabled");
+ waitForAtomicBooleanBecomes(atomicBoolean, false, waitObject,
+ "Touch exploration state listener called when services disabled");
assertFalse("Listener told that touch exploration is disabled, but manager says it enabled",
mAccessibilityManager.isTouchExplorationEnabled());
mAccessibilityManager.removeTouchExplorationStateChangeListener(listener);
@@ -344,19 +381,63 @@
mAccessibilityManager.addTouchExplorationStateChangeListener(listener, mHandler);
mSpeakingAccessibilityServiceRule.enableService();
mVibratingAccessibilityServiceRule.enableService();
- assertAtomicBooleanBecomes(atomicBoolean, true, waitObject,
- "Touch exploration state listener not called when services enabled");
+ waitForAtomicBooleanBecomes(atomicBoolean, true, waitObject,
+ "Touch exploration state listener called when services enabled");
assertTrue("Listener told that touch exploration is enabled, but manager says disabled",
mAccessibilityManager.isTouchExplorationEnabled());
InstrumentedAccessibilityService.disableAllServices();
- assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
- "Touch exploration state listener not called when services disabled");
+ waitForAtomicBooleanBecomes(atomicBoolean, false, waitObject,
+ "Touch exploration state listener called when services disabled");
assertFalse("Listener told that touch exploration is disabled, but manager says it enabled",
mAccessibilityManager.isTouchExplorationEnabled());
mAccessibilityManager.removeTouchExplorationStateChangeListener(listener);
}
@Test
+ public void testAccessibilityServicesStateListenerNoExecutor() {
+ final Object waitObject = new Object();
+ final AtomicBoolean serviceEnabled = new AtomicBoolean(false);
+ final UiAutomation automan = sInstrumentation.getUiAutomation(
+ UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+ final AccessibilityServicesStateChangeListener listener = (AccessibilityManager manager) ->
+ checkServiceEnabled(waitObject, manager, serviceEnabled,
+ VibratingAccessibilityService.class.getSimpleName());
+ try {
+ mAccessibilityManager.addAccessibilityServicesStateChangeListener(listener);
+
+ mVibratingAccessibilityServiceRule.enableService();
+
+ waitForAtomicBooleanBecomes(serviceEnabled, true, waitObject,
+ "Accessibility services state listener called when service is enabled");
+ } finally {
+ automan.destroy();
+ }
+ }
+
+ @Test
+ public void testAccessibilityServicesStateListenerWithExecutor() {
+ final Object waitObject = new Object();
+ final AtomicBoolean serviceEnabled = new AtomicBoolean(false);
+ final UiAutomation automan = sInstrumentation.getUiAutomation(
+ UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+ final AccessibilityServicesStateChangeListener listener = (AccessibilityManager manager) ->
+ checkServiceEnabled(waitObject, manager, serviceEnabled,
+ VibratingAccessibilityService.class.getSimpleName());
+ try {
+ mAccessibilityManager.addAccessibilityServicesStateChangeListener(
+ mTargetContext.getMainExecutor(), listener);
+
+ mVibratingAccessibilityServiceRule.enableService();
+
+ waitForAtomicBooleanBecomes(serviceEnabled, true, waitObject,
+ "Accessibility services state listener called when service is enabled");
+ } finally {
+ automan.destroy();
+ }
+ }
+
+
+ @Test
public void testAccessibilityStateListenerNoHandler() throws Exception {
final Object waitObject = new Object();
final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
@@ -369,19 +450,83 @@
};
mAccessibilityManager.addAccessibilityStateChangeListener(listener);
mSpeakingAndVibratingAccessibilityServiceRule.enableService();
- assertAtomicBooleanBecomes(atomicBoolean, true, waitObject,
- "Accessibility state listener not called when services enabled");
+ waitForAtomicBooleanBecomes(atomicBoolean, true, waitObject,
+ "Accessibility state listener called when services enabled");
assertTrue("Listener told that accessibility is enabled, but manager says disabled",
mAccessibilityManager.isEnabled());
InstrumentedAccessibilityService.disableAllServices();
- assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
- "Accessibility state listener not called when services disabled");
+ waitForAtomicBooleanBecomes(atomicBoolean, false, waitObject,
+ "Accessibility state listener called when services disabled");
assertFalse("Listener told that accessibility is disabled, but manager says enabled",
mAccessibilityManager.isEnabled());
mAccessibilityManager.removeAccessibilityStateChangeListener(listener);
}
@Test
+ public void testAudioDescriptionRequestedChangeListenerWithExecutor() {
+ final UiAutomation automan = sInstrumentation.getUiAutomation(
+ UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+ final Object waitObject = new Object();
+ final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
+
+ AudioDescriptionRequestedChangeListener listener = (boolean b) -> {
+ synchronized (waitObject) {
+ atomicBoolean.set(b);
+ waitObject.notifyAll();
+ }
+ };
+
+ try {
+ mAccessibilityManager.addAudioDescriptionRequestedChangeListener(
+ mTargetContext.getMainExecutor(), listener);
+ putSecureSetting(automan, ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, "1");
+ waitForAtomicBooleanBecomes(atomicBoolean, true, waitObject,
+ "Audio description state listener called when services enabled");
+ assertTrue("Listener told that audio description by default is request.",
+ mAccessibilityManager.isAudioDescriptionRequested());
+
+ putSecureSetting(automan, ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, "0");
+ waitForAtomicBooleanBecomes(atomicBoolean, false, waitObject,
+ "Audio description state listener called when services disabled");
+ assertFalse("Listener told that audio description by default is not request.",
+ mAccessibilityManager.isAudioDescriptionRequested());
+ assertTrue(
+ mAccessibilityManager.removeAudioDescriptionRequestedChangeListener(
+ listener));
+ } finally {
+ automan.destroy();
+ }
+ }
+
+ @Test
+ public void testIsAudioDescriptionEnabled() throws Exception {
+ final UiAutomation automan = sInstrumentation.getUiAutomation(
+ UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+
+ try {
+ putSecureSetting(automan, ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, "1");
+ PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+ @Override
+ public boolean canProceed() {
+ return mAccessibilityManager.isAudioDescriptionRequested();
+ }
+ });
+ assertTrue(mAccessibilityManager.isAudioDescriptionRequested());
+
+ putSecureSetting(automan, ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, "0");
+ PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+ @Override
+ public boolean canProceed() {
+ return !mAccessibilityManager.isAudioDescriptionRequested();
+ }
+ });
+ assertFalse(mAccessibilityManager.isAudioDescriptionRequested());
+ } finally {
+ automan.destroy();
+ }
+ }
+
+ @Test
public void testAccessibilityStateListenerWithHandler() throws Exception {
final Object waitObject = new Object();
final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
@@ -394,13 +539,13 @@
};
mAccessibilityManager.addAccessibilityStateChangeListener(listener, mHandler);
mSpeakingAndVibratingAccessibilityServiceRule.enableService();
- assertAtomicBooleanBecomes(atomicBoolean, true, waitObject,
- "Accessibility state listener not called when services enabled");
+ waitForAtomicBooleanBecomes(atomicBoolean, true, waitObject,
+ "Accessibility state listener called when services enabled");
assertTrue("Listener told that accessibility is enabled, but manager says disabled",
mAccessibilityManager.isEnabled());
InstrumentedAccessibilityService.disableAllServices();
- assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
- "Accessibility state listener not called when services disabled");
+ waitForAtomicBooleanBecomes(atomicBoolean, false, waitObject,
+ "Accessibility state listener called when services disabled");
assertFalse("Listener told that accessibility is disabled, but manager says enabled",
mAccessibilityManager.isEnabled());
mAccessibilityManager.removeAccessibilityStateChangeListener(listener);
@@ -437,18 +582,26 @@
}
}
- private void assertAtomicBooleanBecomes(AtomicBoolean atomicBoolean,
- boolean expectedValue, Object waitObject, String message)
- throws Exception {
- long timeoutTime =
- System.currentTimeMillis() + TIMEOUT_SERVICE_ENABLE;
+ private void checkServiceEnabled(Object waitObject, AccessibilityManager manager,
+ AtomicBoolean serviceEnabled, String serviceName) {
synchronized (waitObject) {
- while ((atomicBoolean.get() != expectedValue)
- && (System.currentTimeMillis() < timeoutTime)) {
- waitObject.wait(timeoutTime - System.currentTimeMillis());
+ List<AccessibilityServiceInfo> infos = manager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ for (AccessibilityServiceInfo info : infos) {
+ final String serviceId = info.getId();
+ if (serviceId.endsWith(serviceName)) {
+ serviceEnabled.set(true);
+ waitObject.notifyAll();
+ }
}
}
- assertTrue(message, atomicBoolean.get() == expectedValue);
+ }
+
+ private void waitForAtomicBooleanBecomes(AtomicBoolean atomicBoolean,
+ boolean expectedValue, Object waitObject, String condition) {
+ long timeoutTime = System.currentTimeMillis() + TIMEOUT_SERVICE_ENABLE;
+ TestUtils.waitOn(waitObject, () -> atomicBoolean.get() == expectedValue, timeoutTime,
+ condition);
}
private void waitForAccessibilityEnabled() throws InterruptedException {
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
index 3b56fb5..7a004ee 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
@@ -18,7 +18,8 @@
import static androidx.test.InstrumentationRegistry.getContext;
-import static org.junit.Assert.assertArrayEquals;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -42,7 +43,6 @@
import android.text.style.ReplacementSpan;
import android.util.ArrayMap;
import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
@@ -113,33 +113,13 @@
}
/**
- * Tests if {@link AccessibilityNodeInfo}s are properly reused.
- */
- @SmallTest
- @Test
- public void testReuse() {
- AccessibilityEvent firstInfo = AccessibilityEvent.obtain();
- firstInfo.recycle();
- AccessibilityEvent secondInfo = AccessibilityEvent.obtain();
- assertSame("AccessibilityNodeInfo not properly reused", firstInfo, secondInfo);
- }
-
- /**
- * Tests if {@link AccessibilityNodeInfo} are properly recycled.
+ * Tests if {@link AccessibilityNodeInfo} can be acquired through obtain(),
+ * and that recycle() can be called on the returned object.
*/
@SmallTest
@Test
public void testRecycle() {
- // obtain and populate an node info
- AccessibilityNodeInfo populatedInfo = AccessibilityNodeInfo.obtain();
- fullyPopulateAccessibilityNodeInfo(populatedInfo);
-
- // recycle and obtain the same recycled instance
- populatedInfo.recycle();
- AccessibilityNodeInfo recycledInfo = AccessibilityNodeInfo.obtain();
-
- // check expectations
- assertAccessibilityNodeInfoCleared(recycledInfo);
+ AccessibilityNodeInfo.obtain().recycle();
}
/**
@@ -328,6 +308,7 @@
// Populate 10 fields
info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
info.setViewIdResourceName("foo.bar:id/baz");
+ info.setUniqueId("foo.bar:id/baz10");
info.setDrawingOrder(5);
info.setAvailableExtraData(
Arrays.asList(AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
@@ -345,7 +326,8 @@
info.setRangeInfo(RangeInfo.obtain(RangeInfo.RANGE_TYPE_FLOAT, 0.05f, 1.0f, 0.01f));
info.setCollectionInfo(
CollectionInfo.obtain(2, 2, true, CollectionInfo.SELECTION_MODE_MULTIPLE));
- info.setCollectionItemInfo(CollectionItemInfo.obtain(1, 2, 3, 4, true, true));
+ info.setCollectionItemInfo(CollectionItemInfo.obtain("RowTitle", 1, 2, "ColumnTitle",
+ 3, 4, true, true));
info.setParent(new View(getContext()));
info.setSource(new View(getContext())); // Populates 2 fields: source and window id
info.setLeashedParent(new MockBinder(), 1); // Populates 2 fields
@@ -471,6 +453,8 @@
receivedInfo.getMovementGranularities());
assertEquals("viewId has incorrect value", expectedInfo.getViewIdResourceName(),
receivedInfo.getViewIdResourceName());
+ assertEquals("Unique id has incorrect value", expectedInfo.getUniqueId(),
+ receivedInfo.getUniqueId());
assertEquals("drawing order has incorrect value", expectedInfo.getDrawingOrder(),
receivedInfo.getDrawingOrder());
assertEquals("Extra data flags have incorrect value", expectedInfo.getAvailableExtraData(),
@@ -544,6 +528,9 @@
assertEquals("CollectionItemInfo#getRowSpan has incorrect value",
expectedItemInfo.getRowSpan(),
receivedItemInfo.getRowSpan());
+ assertThat(expectedItemInfo.getRowTitle()).isEqualTo(receivedItemInfo.getRowTitle());
+ assertThat(
+ expectedItemInfo.getColumnTitle()).isEqualTo(receivedItemInfo.getColumnTitle());
}
// Check 1 field
@@ -650,6 +637,7 @@
assertSame("movementGranularities not properly recycled", 0,
info.getMovementGranularities());
assertNull("viewId not properly recycled", info.getViewIdResourceName());
+ assertNull("Unique id not properly recycled", info.getUniqueId());
assertEquals(0, info.getDrawingOrder());
assertTrue(info.getAvailableExtraData().isEmpty());
assertNull("Pane title not properly recycled", info.getPaneTitle());
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionItemInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionItemInfoTest.java
index 9ffdb0d..e75a698 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionItemInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionItemInfoTest.java
@@ -16,6 +16,8 @@
package android.view.accessibility.cts;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
@@ -50,11 +52,15 @@
c = CollectionItemInfo.obtain(0, 1, 2, 3, true);
assertNotNull(c);
- verifyCollectionItemInfo(c, 0, 1, 2, 3, true, false);
+ verifyCollectionItemInfo(c, null, 0, 1, null, 2, 3, true, false);
c = CollectionItemInfo.obtain(4, 5, 6, 7, true, true);
assertNotNull(c);
- verifyCollectionItemInfo(c, 4, 5, 6, 7, true, true);
+ verifyCollectionItemInfo(c, null, 4, 5, null, 6, 7, true, true);
+
+ c = CollectionItemInfo.obtain("RowTitle", 8, 9, "ColumnTitle", 10, 11, true, true);
+ assertNotNull(c);
+ verifyCollectionItemInfo(c, "RowTitle", 8, 9, "ColumnTitle", 10, 11, true, true);
}
@SmallTest
@@ -63,20 +69,34 @@
CollectionItemInfo c;
c = new CollectionItemInfo(0, 1, 2, 3, true);
- verifyCollectionItemInfo(c, 0, 1, 2, 3, true, false);
+ verifyCollectionItemInfo(c, null, 0, 1, null, 2, 3, true, false);
c = new CollectionItemInfo(4, 5, 6, 7, true, true);
- verifyCollectionItemInfo(c, 4, 5, 6, 7, true, true);
+ verifyCollectionItemInfo(c, null, 4, 5, null, 6, 7, true, true);
+ }
+
+ @SmallTest
+ @Test
+ public void testBuilder() {
+ CollectionItemInfo.Builder builder = new CollectionItemInfo.Builder();
+
+ CollectionItemInfo collectionItemInfo = builder.setRowTitle("RowTitle").setRowIndex(
+ 0).setRowSpan(1).setColumnTitle("ColumnTitle").setColumnIndex(2).setColumnSpan(
+ 3).setHeading(true).setSelected(true).build();
+ verifyCollectionItemInfo(collectionItemInfo, "RowTitle", 0, 1, "ColumnTitle", 2,
+ 3, true, true);
}
/**
* Verifies all properties of the <code>info</code> with input expected values.
*/
public static void verifyCollectionItemInfo(AccessibilityNodeInfo.CollectionItemInfo info,
- int rowIndex, int rowSpan, int columnIndex, int columnSpan, boolean heading,
- boolean selected) {
+ String rowTitle, int rowIndex, int rowSpan, String columnTitle, int columnIndex,
+ int columnSpan, boolean heading, boolean selected) {
+ assertThat(rowTitle).isEqualTo(info.getRowTitle());
assertEquals(rowIndex, info.getRowIndex());
assertEquals(rowSpan, info.getRowSpan());
+ assertThat(columnTitle).isEqualTo(info.getColumnTitle());
assertEquals(columnIndex, info.getColumnIndex());
assertEquals(columnSpan, info.getColumnSpan());
assertSame(heading, info.isHeading());
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
index 08d6637..c3c5641 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
@@ -23,6 +23,7 @@
import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.os.Message;
import android.platform.test.annotations.Presubmit;
+import android.view.Display;
import android.view.accessibility.AccessibilityRecord;
import androidx.test.filters.SmallTest;
@@ -104,6 +105,8 @@
TestCase.assertSame("scrollX not properly recycled", 0, record.getScrollX());
TestCase.assertSame("scrollY not properly recycled", 0, record.getScrollY());
TestCase.assertSame("toIndex not properly recycled", -1, record.getToIndex());
+ TestCase.assertSame("displayId not properly recycled", Display.INVALID_DISPLAY,
+ record.getDisplayId());
}
/**
@@ -132,6 +135,7 @@
record.setScrollY(1);
record.setToIndex(1);
record.setScrollable(true);
+ record.setDisplayId(Display.DEFAULT_DISPLAY);
}
static void assertEqualAccessibilityRecord(AccessibilityRecord expectedRecord,
@@ -173,6 +177,8 @@
receivedRecord.getToIndex());
assertSame("scrollable has incorrect value", expectedRecord.isScrollable(),
receivedRecord.isScrollable());
+ assertSame("displayId has incorrect value", expectedRecord.getDisplayId(),
+ receivedRecord.getDisplayId());
assertFalse("one of the parcelableData is null",
expectedRecord.getParcelableData() == null
diff --git a/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java b/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java
index c3054ef..c6376af 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java
@@ -22,6 +22,7 @@
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.mockito.Matchers.anyFloat;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.mock;
@@ -29,8 +30,10 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import android.Manifest;
import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.app.UiAutomation;
+import android.content.res.Resources;
import android.os.ParcelFileDescriptor;
import android.view.accessibility.CaptioningManager;
import android.view.accessibility.CaptioningManager.CaptionStyle;
@@ -82,6 +85,10 @@
putSecureSetting("accessibility_captioning_preset", "1");
putSecureSetting("accessibility_captioning_locale", "en_US");
putSecureSetting("accessibility_captioning_font_scale", "1.0");
+ // TODO (b209352162): We need to backup and restore the original values for
+ // these two setting keys.
+ putSecureSetting("odi_captions_enabled", "0");
+ putSecureSetting("odi_captions_volume_ui_enabled", "0");
CaptioningChangeListener mockListener = mock(CaptioningChangeListener.class);
mManager.addCaptioningChangeListener(mockListener);
@@ -100,11 +107,19 @@
putSecureSetting("accessibility_captioning_font_scale", "2.0");
verify(mockListener, timeout(LISTENER_TIMEOUT)).onFontScaleChanged(anyFloat());
+ putSecureSetting("odi_captions_enabled", "1");
+ verify(mockListener, timeout(LISTENER_TIMEOUT)).onSystemAudioCaptioningChanged(true);
+
+ putSecureSetting("odi_captions_volume_ui_enabled", "1");
+ verify(mockListener, timeout(LISTENER_TIMEOUT)).onSystemAudioCaptioningUiChanged(true);
+
mManager.removeCaptioningChangeListener(mockListener);
Mockito.reset(mockListener);
putSecureSetting("accessibility_captioning_enabled","0");
+ putSecureSetting("odi_captions_enabled", "0");
+ putSecureSetting("odi_captions_volume_ui_enabled", "0");
verifyZeroInteractions(mockListener);
try {
@@ -145,6 +160,52 @@
assertNull("Default user style has no typeface", userStyle.getTypeface());
}
+ @Test
+ public void testIsCallCaptioningEnabled() {
+ Resources resources =
+ getInstrumentation().getTargetContext().getResources();
+ // com.android.internal.R is not visible to this test so we need
+ // to query for the resource id.
+ int resourceId = resources.getIdentifier(
+ "config_systemCaptionsServiceCallsEnabled", "bool", "android");
+
+ boolean expected = resources.getBoolean(resourceId);
+
+ boolean actual = mManager.isCallCaptioningEnabled();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testSetSystemAudioCaptionWithoutPermission_throwSecurityException() {
+ mManager.setSystemAudioCaptioningRequested(true);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testSetSystemAudioCaptionUiWithoutPermission_throwSecurityException() {
+ mManager.setSystemAudioCaptioningUiRequested(true);
+ }
+
+ @Test
+ public void testSystemAudioCaption() {
+ putSecureSetting("odi_captions_enabled", "0");
+ putSecureSetting("odi_captions_volume_ui_enabled", "0");
+ mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION);
+ try {
+ mManager.setSystemAudioCaptioningRequested(true);
+ assertTrue("Test runner set system audio caption enabled to true",
+ mManager.isSystemAudioCaptioningRequested());
+
+ mManager.setSystemAudioCaptioningUiRequested(true);
+ assertTrue("Test runner set system audio caption ui enabled to true",
+ mManager.isSystemAudioCaptioningUiRequested());
+ } finally {
+ mUiAutomation.dropShellPermissionIdentity();
+ putSecureSetting("odi_captions_enabled", "0");
+ putSecureSetting("odi_captions_volume_ui_enabled", "0");
+ }
+ }
+
private void deleteSecureSetting(String name) {
execShellCommand("settings delete secure " + name);
}
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index 84981cd..584a8d5 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -25,6 +25,7 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application android:theme="@android:style/Theme.Holo.NoActionBar"
android:requestLegacyExternalStorage="true">
@@ -57,6 +58,11 @@
android:supportsPictureInPicture="true"
android:screenOrientation="locked"/>
+ <activity android:label="Activity for testing window accessibility reporting"
+ android:name=".activities.NotTouchableWindowTestActivity"
+ android:process=":NotTouchableWindowTestActivity"
+ android:exported="true"/>
+
<activity android:label="@string/non_default_display_activity"
android:name=".activities.NonDefaultDisplayActivity"
android:screenOrientation="locked"/>
@@ -77,6 +83,9 @@
<activity android:label="@string/accessibility_drag_and_drop_test_activity"
android:name=".activities.AccessibilityDragAndDropActivity"
android:screenOrientation="locked"/>
+ <activity android:label="@string/accessibility_cache_activity"
+ android:name=".activities.AccessibilityCacheActivity"
+ android:screenOrientation="locked"/>
<service android:name=".StubSystemActionsAccessibilityService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
@@ -194,6 +203,17 @@
<meta-data android:name="android.accessibilityservice"
android:resource="@xml/stub_focus_indicator_service"/>
</service>
+
+ <service android:name=".StubInputMethod"
+ android:label="@string/ime_name"
+ android:permission="android.permission.BIND_INPUT_METHOD"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.view.InputMethod"/>
+ </intent-filter>
+ <meta-data android:name="android.view.im"
+ android:resource="@xml/stub_ime"/>
+ </service>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/accessibilityservice/res/layout/accessibility_cache.xml b/tests/accessibilityservice/res/layout/accessibility_cache.xml
new file mode 100644
index 0000000..07b7f9c
--- /dev/null
+++ b/tests/accessibilityservice/res/layout/accessibility_cache.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal">
+ <TextView
+ android:text="textView"
+ android:layout_width="60dp"
+ android:layout_height="60dp"
+ android:layout_margin="60dp"/>
+ </LinearLayout>
+</LinearLayout>
diff --git a/tests/accessibilityservice/res/values/strings.xml b/tests/accessibilityservice/res/values/strings.xml
index 3d3b6be..bbc55619 100644
--- a/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/accessibilityservice/res/values/strings.xml
@@ -199,4 +199,8 @@
<string name="accessibility_drag_and_drop_test_activity">Drag and drop</string>
<string name="drag_and_drop_text">Dragged text</string>
+ <!-- String label of the input method -->
+ <string name="ime_name">InputMethod</string>
+
+ <string name="accessibility_cache_activity">Cache activity</string>
</resources>
diff --git a/tests/accessibilityservice/res/xml/stub_ime.xml b/tests/accessibilityservice/res/xml/stub_ime.xml
new file mode 100644
index 0000000..9810de4
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/stub_ime.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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"
+ android:supportsSwitchingToNextInputMethod="true">
+ <subtype
+ android:imeSubtypeMode="keyboard"
+ android:isAsciiCapable="true"
+ android:overridesImplicitlyEnabledSubtype="true"
+ />
+ <subtype
+ android:imeSubtypeLocale="en_US"
+ android:imeSubtypeMode="keyboard"
+ android:isAsciiCapable="true"
+ />
+ <subtype
+ android:imeSubtypeLocale="ru_RU"
+ android:imeSubtypeMode="keyboard"
+ android:isAsciiCapable="false"
+ />
+</input-method>
\ No newline at end of file
diff --git a/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml b/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml
index 5797079..c12f8c7 100644
--- a/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml
+++ b/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml
@@ -20,7 +20,7 @@
android:description="@string/stub_touch_exploration_a11y_service_description"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
- android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode|flagReportViewIds|flagSendMotionEvents"
+ android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode|flagReportViewIds|flagSendMotionEvents|flagRetrieveInteractiveWindows"
android:canRequestTouchExplorationMode="true"
android:canRetrieveWindowContent="true"
android:canPerformGestures="true" />
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityCacheTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityCacheTest.java
new file mode 100644
index 0000000..08710d6
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityCacheTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
+import android.accessibilityservice.cts.activities.AccessibilityCacheActivity;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.platform.test.annotations.AppModeFull;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+@AppModeFull
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityCacheTest {
+ private static Instrumentation sInstrumentation;
+ private static UiAutomation sUiAutomation;
+
+ private InstrumentedAccessibilityService mService;
+ private AccessibilityCacheActivity mActivity;
+
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ private final ActivityTestRule<AccessibilityCacheActivity> mActivityRule =
+ new ActivityTestRule<>(AccessibilityCacheActivity.class, false, false);
+
+ private InstrumentedAccessibilityServiceTestRule<InstrumentedAccessibilityService>
+ mInstrumentedAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ InstrumentedAccessibilityService.class, false);
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ .around(mInstrumentedAccessibilityServiceRule)
+ .around(mDumpOnFailureRule);
+
+ @BeforeClass
+ public static void oneTimeSetup() throws Exception {
+ sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ sUiAutomation = sInstrumentation.getUiAutomation();
+ }
+
+ @AfterClass
+ public static void postTestTearDown() {
+ sUiAutomation.destroy();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mService = mInstrumentedAccessibilityServiceRule.enableService();
+ mActivity = launchActivityAndWaitForItToBeOnscreen(
+ sInstrumentation, sUiAutomation, mActivityRule);
+ }
+
+ @Test
+ public void enable_cacheEnabled() {
+ assertTrue(mService.setCacheEnabled(false));
+ assertFalse("Failed to disable", mService.isCacheEnabled());
+
+ assertTrue(mService.setCacheEnabled(true));
+ assertTrue("Failed to enable", mService.isCacheEnabled());
+ }
+
+ @Test
+ public void disable_cacheDisabled() {
+ assertTrue(mService.setCacheEnabled(false));
+ assertFalse("Failed to disable", mService.isCacheEnabled());
+ }
+
+ @Test
+ public void queryNode_nodeIsInCache() {
+ AccessibilityNodeInfo info = mService.getRootInActiveWindow();
+ assertTrue("Node is not in cache", mService.isNodeInCache(info));
+ }
+
+ @Test
+ public void invalidateNode_nodeInCacheInvalidated() {
+ AccessibilityNodeInfo info = mService.getRootInActiveWindow();
+ assertTrue(mService.clearCachedSubtree(info));
+ assertFalse("Node is still in cache", mService.isNodeInCache(info));
+ }
+
+ @Test
+ public void invalidateNode_subtreeInCacheInvalidated() {
+ // Tree is FrameLayout with 1 TextView child
+ AccessibilityNodeInfo root = mService.getRootInActiveWindow();
+ assertThat(root.getChildCount(), is(1));
+ AccessibilityNodeInfo child = root.getChild(0);
+
+ assertTrue(mService.clearCachedSubtree(root));
+
+ assertFalse("Root is in cache", mService.isNodeInCache(root));
+ assertFalse("Child 1 is in cache", mService.isNodeInCache(child));
+ }
+
+ @Test
+ public void clear_cacheInvalidated() {
+ // Tree is FrameLayout with 1 TextView child
+ AccessibilityNodeInfo root = mService.getRootInActiveWindow();
+ assertThat(root.getChildCount(), is(1));
+ AccessibilityNodeInfo child = root.getChild(0);
+
+ assertTrue(mService.clearCache());
+
+ assertFalse("Root is in cache", mService.isNodeInCache(root));
+ assertFalse("Child 1 is in cache", mService.isNodeInCache(child));
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index 1b003e3..5f9733c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -16,6 +16,7 @@
package android.accessibilityservice.cts;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.accessibility.cts.common.InstrumentedAccessibilityService.enableService;
import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType;
import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithAction;
@@ -99,6 +100,7 @@
import com.android.compatibility.common.util.CtsMouseUtil;
+import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -159,10 +161,16 @@
@Before
public void setUp() throws Exception {
+ sUiAutomation.adoptShellPermissionIdentity(POST_NOTIFICATIONS);
mActivity = launchActivityAndWaitForItToBeOnscreen(
sInstrumentation, sUiAutomation, mActivityRule);
}
+ @After
+ public void tearDown() throws Exception {
+ sUiAutomation.dropShellPermissionIdentity();
+ }
+
@MediumTest
@Presubmit
@Test
@@ -172,6 +180,7 @@
expected.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
expected.setClassName(ListView.class.getName());
expected.setPackageName(mActivity.getPackageName());
+ expected.setDisplayId(mActivity.getDisplayId());
expected.getText().add(mActivity.getString(R.string.second_list_item));
expected.setItemCount(2);
expected.setCurrentItemIndex(1);
@@ -215,6 +224,7 @@
expected.setEventType(AccessibilityEvent.TYPE_VIEW_CLICKED);
expected.setClassName(Button.class.getName());
expected.setPackageName(mActivity.getPackageName());
+ expected.setDisplayId(mActivity.getDisplayId());
expected.getText().add(mActivity.getString(R.string.button_title));
expected.setEnabled(true);
@@ -253,6 +263,7 @@
expected.setEventType(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
expected.setClassName(Button.class.getName());
expected.setPackageName(mActivity.getPackageName());
+ expected.setDisplayId(mActivity.getDisplayId());
expected.getText().add(mActivity.getString(R.string.button_title));
expected.setEnabled(true);
@@ -291,6 +302,7 @@
expected.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
expected.setClassName(Button.class.getName());
expected.setPackageName(mActivity.getPackageName());
+ expected.setDisplayId(mActivity.getDisplayId());
expected.getText().add(mActivity.getString(R.string.button_title));
expected.setItemCount(5);
expected.setCurrentItemIndex(3);
@@ -345,6 +357,7 @@
expected.setEventType(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
expected.setClassName(EditText.class.getName());
expected.setPackageName(mActivity.getPackageName());
+ expected.setDisplayId(mActivity.getDisplayId());
expected.getText().add(afterText);
expected.setBeforeText(beforeText);
expected.setFromIndex(3);
@@ -385,6 +398,7 @@
expected.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
expected.setClassName(AlertDialog.class.getName());
expected.setPackageName(mActivity.getPackageName());
+ expected.setDisplayId(mActivity.getDisplayId());
expected.getText().add(mActivity.getString(R.string.alert_title));
expected.getText().add(mActivity.getString(R.string.alert_message));
expected.setEnabled(true);
@@ -415,6 +429,25 @@
}
@MediumTest
+ @Presubmit
+ @Test
+ public void testTypeWindowsChangedAccessibilityEvent() throws Throwable {
+ // create and populate the expected event
+ final AccessibilityEvent expected = AccessibilityEvent.obtain();
+ expected.setEventType(AccessibilityEvent.TYPE_WINDOWS_CHANGED);
+ expected.setDisplayId(mActivity.getDisplayId());
+
+ // check the received event
+ AccessibilityEvent awaitedEvent =
+ sUiAutomation.executeAndWaitForEvent(
+ () -> mActivity.runOnUiThread(() -> mActivity.finish()),
+ event -> event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_REMOVED
+ && equalsAccessiblityEvent(event, expected),
+ DEFAULT_TIMEOUT_MS);
+ assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
+ }
+
+ @MediumTest
@AppModeFull
@SuppressWarnings("deprecation")
@Presubmit
@@ -1108,6 +1141,7 @@
&& first.getScrollX() == second.getScrollX()
&& first.getScrollY() == second.getScrollY()
&& first.getAddedCount() == second.getAddedCount()
+ && first.getDisplayId() == second.getDisplayId()
&& TextUtils.equals(first.getBeforeText(), second.getBeforeText())
&& TextUtils.equals(first.getClassName(), second.getClassName())
&& TextUtils.equals(first.getContentDescription(), second.getContentDescription())
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
index 25f4853..b5b5766 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
@@ -16,8 +16,6 @@
package android.accessibilityservice.cts;
-import static android.accessibility.cts.common.ShellCommandBuilder.execShellCommand;
-import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.AM_BROADCAST_CLOSE_SYSTEM_DIALOG_COMMAND;
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.homeScreenOrBust;
import static org.junit.Assert.assertTrue;
@@ -174,7 +172,6 @@
// Ideally should verify that we actually have a screenshot, but it's also possible
// for the screenshot to fail
waitForIdle();
- execShellCommand(sUiAutomation, AM_BROADCAST_CLOSE_SYSTEM_DIALOG_COMMAND);
}
private void waitForIdle() throws TimeoutException {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
index 659aaf7..c1396d9 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
@@ -16,12 +16,10 @@
package android.accessibilityservice.cts;
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.compatibility.common.util.TestUtils.waitOn;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -30,6 +28,7 @@
import static org.mockito.Mockito.anyFloat;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
@@ -40,6 +39,7 @@
import android.accessibilityservice.AccessibilityService.MagnificationController;
import android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.MagnificationConfig;
import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
import android.app.Activity;
import android.app.Instrumentation;
@@ -51,22 +51,31 @@
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
import android.widget.Button;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.TestUtils;
+
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * Class for testing {@link AccessibilityServiceInfo}.
+ * Class for testing {@link MagnificationController} and the magnification overlay window.
*/
@AppModeFull
@RunWith(AndroidJUnit4.class)
@@ -74,8 +83,13 @@
/** Maximum timeout when waiting for a magnification callback. */
public static final int LISTENER_TIMEOUT_MILLIS = 500;
+ /** Maximum animation timeout when waiting for a magnification callback. */
+ public static final int LISTENER_ANIMATION_TIMEOUT_MILLIS = 1000;
public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED =
"accessibility_display_magnification_enabled";
+
+ private static UiAutomation sUiAutomation;
+
private StubMagnificationAccessibilityService mService;
private Instrumentation mInstrumentation;
@@ -100,12 +114,26 @@
.around(mInstrumentedAccessibilityServiceRule)
.around(mDumpOnFailureRule);
+ @BeforeClass
+ public static void oneTimeSetUp() {
+ sUiAutomation = InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+ final AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
+ info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ sUiAutomation.setServiceInfo(info);
+ }
+
+ @AfterClass
+ public static void postTestTearDown() {
+ sUiAutomation.destroy();
+ }
+
@Before
public void setUp() throws Exception {
- ShellCommandBuilder.create(getInstrumentation())
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ ShellCommandBuilder.create(sUiAutomation)
.deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
.run();
- mInstrumentation = getInstrumentation();
// Starting the service will force the accessibility subsystem to examine its settings, so
// it will update magnification in the process to disable it.
mService = mMagnificationAccessibilityServiceRule.enableService();
@@ -159,8 +187,151 @@
}
@Test
+ public void testSetMagnificationConfig_expectedConfig() throws Exception {
+ final MagnificationController controller = mService.getMagnificationController();
+ final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
+ WindowManager.class);
+ final float scale = 2.0f;
+ final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
+ final float y = windowManager.getCurrentWindowMetrics().getBounds().centerY();
+ final AtomicBoolean setConfig = new AtomicBoolean();
+
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(MAGNIFICATION_MODE_WINDOW)
+ .setScale(scale)
+ .setCenterX(x)
+ .setCenterY(y).build();
+
+ mService.runOnServiceSync(() -> {
+ setConfig.set(controller.setMagnificationConfig(config, false));
+ });
+ waitUntilMagnificationConfig(controller, MAGNIFICATION_MODE_WINDOW, x, y);
+
+ assertTrue("Failed to set config", setConfig.get());
+ assertConfigEquals(config, controller.getMagnificationConfig());
+
+ final float newScale = scale + 1;
+ final Region region = controller.getMagnificationRegion();
+ final Rect bounds = region.getBounds();
+ final float newX = bounds.left + (bounds.width() / 4.0f);
+ final float newY = bounds.top + (bounds.height() / 4.0f);
+ final MagnificationConfig newConfig = new MagnificationConfig.Builder()
+ .setMode(MAGNIFICATION_MODE_FULLSCREEN).setScale(newScale).setCenterX(
+ newX).setCenterY(
+ newY).build();
+ mService.runOnServiceSync(() -> {
+ controller.setMagnificationConfig(newConfig, false);
+ });
+ waitUntilMagnificationConfig(controller, MAGNIFICATION_MODE_FULLSCREEN, newX, newY);
+
+ assertTrue("Failed to set config", setConfig.get());
+ assertConfigEquals(newConfig, controller.getMagnificationConfig());
+ }
+
+ @Test
+ public void testSetMagnificationConfig_legacyApiExpectedResult() {
+ final MagnificationController controller = mService.getMagnificationController();
+ final Region region = controller.getMagnificationRegion();
+ final Rect bounds = region.getBounds();
+ final float scale = 2.0f;
+ final float x = bounds.left + (bounds.width() / 4.0f);
+ final float y = bounds.top + (bounds.height() / 4.0f);
+ final AtomicBoolean setConfig = new AtomicBoolean();
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(MAGNIFICATION_MODE_FULLSCREEN)
+ .setScale(scale)
+ .setCenterX(x)
+ .setCenterY(y).build();
+ try {
+ mService.runOnServiceSync(() -> {
+ setConfig.set(controller.setMagnificationConfig(config, false));
+ });
+
+ assertEquals("Failed to apply scale", scale, controller.getScale(), 0f);
+ assertEquals("Failed to apply center X", x, controller.getCenterX(), 5.0f);
+ assertEquals("Failed to apply center Y", y, controller.getCenterY(), 5.0f);
+ } finally {
+ final MagnificationConfig resetConfig = new MagnificationConfig.Builder()
+ .setScale(1).build();
+ mService.runOnServiceSync(() -> {
+ controller.setMagnificationConfig(resetConfig, false);
+ });
+ }
+ }
+
+ @Test
+ public void testSetWindowModeConfig_hasMagnificationOverlay() throws TimeoutException {
+ final MagnificationController controller = mService.getMagnificationController();
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(MAGNIFICATION_MODE_WINDOW)
+ .setScale(2.0f)
+ .build();
+
+ try {
+ sUiAutomation.executeAndWaitForEvent(
+ () -> controller.setMagnificationConfig(config, false),
+ event -> sUiAutomation.getWindows().stream().anyMatch(
+ accessibilityWindowInfo -> accessibilityWindowInfo.getType()
+ == AccessibilityWindowInfo.TYPE_MAGNIFICATION_OVERLAY), 5000);
+ } finally {
+ controller.resetCurrentMagnification(false);
+ }
+ }
+
+ @Test
+ public void testGetMagnificationConfig_setConfigByLegacyApi_expectedResult() {
+ final MagnificationController controller = mService.getMagnificationController();
+ final Region region = controller.getMagnificationRegion();
+ final Rect bounds = region.getBounds();
+ final float scale = 2.0f;
+ final float x = bounds.left + (bounds.width() / 4.0f);
+ final float y = bounds.top + (bounds.height() / 4.0f);
+ mService.runOnServiceSync(() -> {
+ controller.setScale(scale, false);
+ controller.setCenter(x, y, false);
+ });
+
+ final MagnificationConfig config = controller.getMagnificationConfig();
+
+ assertEquals("Failed to apply scale", scale, config.getScale(), 0f);
+ assertEquals("Failed to apply center X", x, config.getCenterX(), 5.0f);
+ assertEquals("Failed to apply center Y", y, config.getCenterY(), 5.0f);
+ }
+
+ @Test
public void testListener() {
final MagnificationController controller = mService.getMagnificationController();
+ final OnMagnificationChangedListener spyListener = mock(
+ OnMagnificationChangedListener.class);
+ final OnMagnificationChangedListener listener =
+ (controller1, region, scale, centerX, centerY) ->
+ spyListener.onMagnificationChanged(controller1, region, scale, centerX,
+ centerY);
+ controller.addListener(listener);
+
+ try {
+ final float scale = 2.0f;
+ final AtomicBoolean result = new AtomicBoolean();
+
+ mService.runOnServiceSync(() -> result.set(controller.setScale(scale, false)));
+
+ assertTrue("Failed to set scale", result.get());
+ verify(spyListener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
+ eq(controller), any(Region.class), eq(scale), anyFloat(), anyFloat());
+
+ mService.runOnServiceSync(() -> result.set(controller.reset(false)));
+
+ assertTrue("Failed to reset", result.get());
+ verify(spyListener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
+ eq(controller), any(Region.class), eq(1.0f), anyFloat(), anyFloat());
+ } finally {
+ controller.removeListener(listener);
+ }
+ }
+
+ @Test
+ public void testListener_changeConfigByLegacyApi_notifyConfigChanged() {
+ final MagnificationController controller = mService.getMagnificationController();
final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
controller.addListener(listener);
@@ -171,21 +342,124 @@
mService.runOnServiceSync(() -> result.set(controller.setScale(scale, false)));
assertTrue("Failed to set scale", result.get());
- verify(listener, timeout(LISTENER_TIMEOUT_MILLIS).atLeastOnce()).onMagnificationChanged(
- eq(controller), any(Region.class), eq(scale), anyFloat(), anyFloat());
+ final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
+ MagnificationConfig.class);
+ verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
+ eq(controller), any(Region.class), configCaptor.capture());
+ assertEquals(scale, configCaptor.getValue().getScale(), 0);
+ reset(listener);
mService.runOnServiceSync(() -> result.set(controller.reset(false)));
assertTrue("Failed to reset", result.get());
- verify(listener, timeout(LISTENER_TIMEOUT_MILLIS).atLeastOnce()).onMagnificationChanged(
- eq(controller), any(Region.class), eq(1.0f), anyFloat(), anyFloat());
+ verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
+ eq(controller), any(Region.class), configCaptor.capture());
+ assertEquals(1.0f, configCaptor.getValue().getScale(), 0);
} finally {
controller.removeListener(listener);
}
}
@Test
- public void testMagnificationServiceShutsDownWhileMagnifying_shouldReturnTo1x() {
+ public void testListener_magnificationConfigChangedWithoutAnimation_notifyConfigChanged()
+ throws Exception {
+ final MagnificationController controller = mService.getMagnificationController();
+ final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
+ controller.addListener(listener);
+ final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
+ WindowManager.class);
+ final float scale = 2.0f;
+ final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
+ final float y = windowManager.getCurrentWindowMetrics().getBounds().centerY();
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(MAGNIFICATION_MODE_WINDOW)
+ .setScale(scale)
+ .setCenterX(x)
+ .setCenterY(y).build();
+ final AtomicBoolean setConfig = new AtomicBoolean();
+
+ try {
+ mService.runOnServiceSync(() -> {
+ setConfig.set(controller.setMagnificationConfig(config, false));
+ });
+ TestUtils.waitUntil("Failed to set config", () -> setConfig.get());
+
+ final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
+ MagnificationConfig.class);
+ verify(listener, timeout(LISTENER_TIMEOUT_MILLIS).atLeastOnce()).onMagnificationChanged(
+ eq(controller), any(Region.class), configCaptor.capture());
+ assertConfigEquals(config, configCaptor.getValue());
+
+ final float newScale = scale + 1;
+ final float newX = x + 10;
+ final float newY = y + 10;
+ final MagnificationConfig fullscreenConfig = new MagnificationConfig.Builder()
+ .setMode(MAGNIFICATION_MODE_FULLSCREEN)
+ .setScale(newScale)
+ .setCenterX(newX)
+ .setCenterY(newY).build();
+
+ reset(listener);
+ mService.runOnServiceSync(() -> {
+ setConfig.set(controller.setMagnificationConfig(fullscreenConfig, false));
+ });
+ TestUtils.waitUntil("Failed to set config", () -> setConfig.get());
+
+ verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
+ eq(controller), any(Region.class), configCaptor.capture());
+ assertConfigEquals(fullscreenConfig, configCaptor.getValue());
+ } finally {
+ final MagnificationConfig resetConfig = new MagnificationConfig.Builder()
+ .setScale(1).build();
+ mService.runOnServiceSync(() -> {
+ controller.setMagnificationConfig(resetConfig, false);
+ controller.removeListener(listener);
+ });
+ }
+ }
+
+ @Test
+ public void testListener_magnificationConfigChangedWithAnimation_notifyConfigChanged() {
+ final MagnificationController controller = mService.getMagnificationController();
+ final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
+ controller.addListener(listener);
+ final float scale = 2.0f;
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(MAGNIFICATION_MODE_WINDOW)
+ .setScale(scale).build();
+
+ try {
+ mService.runOnServiceSync(() -> {
+ controller.setMagnificationConfig(config, /* animate= */ true);
+ });
+
+ verify(listener, timeout(LISTENER_ANIMATION_TIMEOUT_MILLIS)).onMagnificationChanged(
+ eq(controller), any(Region.class), any(MagnificationConfig.class));
+
+ final float newScale = scale + 1;
+ final MagnificationConfig fullscreenConfig = new MagnificationConfig.Builder()
+ .setMode(MAGNIFICATION_MODE_FULLSCREEN)
+ .setScale(newScale).build();
+
+ reset(listener);
+ mService.runOnServiceSync(() -> {
+ controller.setMagnificationConfig(fullscreenConfig, /* animate= */ true);
+ });
+
+ verify(listener, timeout(LISTENER_ANIMATION_TIMEOUT_MILLIS)).onMagnificationChanged(
+ eq(controller), any(Region.class), any(MagnificationConfig.class));
+ } finally {
+ final MagnificationConfig resetConfig = new MagnificationConfig.Builder()
+ .setScale(1).build();
+ mService.runOnServiceSync(() -> {
+ controller.setMagnificationConfig(resetConfig, false);
+ controller.removeListener(listener);
+ });
+ }
+ }
+
+ @Test
+ public void testMagnificationServiceShutsDownWhileMagnifying_fullscreen_shouldReturnTo1x() {
final MagnificationController controller = mService.getMagnificationController();
mService.runOnServiceSync(() -> controller.setScale(2.0f, false));
@@ -199,6 +473,36 @@
}
@Test
+ public void testMagnificationServiceShutsDownWhileMagnifying_windowMode_shouldReturnTo1x()
+ throws Exception {
+ final MagnificationController controller = mService.getMagnificationController();
+ final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
+ WindowManager.class);
+ final float scale = 2.0f;
+ final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
+ final float y = windowManager.getCurrentWindowMetrics().getBounds().centerX();
+
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(MAGNIFICATION_MODE_WINDOW)
+ .setScale(scale)
+ .setCenterX(x)
+ .setCenterY(y).build();
+
+ mService.runOnServiceSync(() -> {
+ controller.setMagnificationConfig(config, false);
+ });
+ waitUntilMagnificationConfig(controller, MAGNIFICATION_MODE_WINDOW, x, y);
+
+ mService.runOnServiceSync(() -> mService.disableSelf());
+ mService = null;
+ InstrumentedAccessibilityService service =
+ mInstrumentedAccessibilityServiceRule.enableService();
+ final MagnificationController controller2 = service.getMagnificationController();
+ assertEquals("Magnification must reset when a service dies",
+ 1.0f, controller2.getMagnificationConfig().getScale(), 0f);
+ }
+
+ @Test
public void testGetMagnificationRegion_whenCanControlMagnification_shouldNotBeEmpty() {
final MagnificationController controller = mService.getMagnificationController();
Region magnificationRegion = controller.getMagnificationRegion();
@@ -220,7 +524,7 @@
@Test
public void testGetMagnificationRegion_whenMagnificationGesturesEnabled_shouldNotBeEmpty() {
- ShellCommandBuilder.create(mInstrumentation)
+ ShellCommandBuilder.create(sUiAutomation)
.putSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, "1")
.run();
mService.runOnServiceSync(() -> mService.disableSelf());
@@ -233,13 +537,103 @@
assertFalse("Magnification region should not be empty when magnification "
+ "gestures are active", magnificationRegion.isEmpty());
} finally {
- ShellCommandBuilder.create(mInstrumentation)
+ ShellCommandBuilder.create(sUiAutomation)
.deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
.run();
}
}
@Test
+ public void testGetCurrentMagnificationRegion_fullscreen_exactRegionCenter() throws Exception {
+ final MagnificationController controller = mService.getMagnificationController();
+ final Region region = controller.getMagnificationRegion();
+ final Rect bounds = region.getBounds();
+ final float scale = 2.0f;
+ final float x = bounds.left + (bounds.width() / 4.0f);
+ final float y = bounds.top + (bounds.height() / 4.0f);
+
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(MAGNIFICATION_MODE_FULLSCREEN)
+ .setScale(scale)
+ .setCenterX(x)
+ .setCenterY(y).build();
+ try {
+ mService.runOnServiceSync(() -> {
+ controller.setMagnificationConfig(config, false);
+ });
+ waitUntilMagnificationConfig(controller, MAGNIFICATION_MODE_FULLSCREEN, x, y);
+
+ final Region magnificationRegion = controller.getCurrentMagnificationRegion();
+ assertFalse(magnificationRegion.isEmpty());
+ } finally {
+ mService.runOnServiceSync(() -> {
+ controller.resetCurrentMagnification(false);
+ });
+ }
+ }
+
+ @Test
+ public void testGetCurrentMagnificationRegion_windowMode_exactRegionCenter() throws Exception {
+ final MagnificationController controller = mService.getMagnificationController();
+ final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
+ WindowManager.class);
+ final float scale = 2.0f;
+ final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
+ final float y = windowManager.getCurrentWindowMetrics().getBounds().centerY();
+
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(MAGNIFICATION_MODE_WINDOW)
+ .setScale(scale)
+ .setCenterX(x)
+ .setCenterY(y).build();
+ try {
+ mService.runOnServiceSync(() -> {
+ controller.setMagnificationConfig(config, false);
+ });
+ waitUntilMagnificationConfig(controller, MAGNIFICATION_MODE_WINDOW, x, y);
+
+ final Region magnificationRegion = controller.getCurrentMagnificationRegion();
+ final Rect magnificationBounds = magnificationRegion.getBounds();
+ assertEquals(magnificationBounds.exactCenterX(), x, 0);
+ assertEquals(magnificationBounds.exactCenterY(), y, 0);
+ } finally {
+ mService.runOnServiceSync(() -> {
+ controller.resetCurrentMagnification(false);
+ });
+ }
+ }
+
+ @Test
+ public void testResetCurrentMagnificationRegion_resetWindowMode() throws Exception {
+ final MagnificationController controller = mService.getMagnificationController();
+ final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
+ WindowManager.class);
+ final float scale = 2.0f;
+ final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
+ final float y = windowManager.getCurrentWindowMetrics().getBounds().centerY();
+
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(MAGNIFICATION_MODE_WINDOW)
+ .setScale(scale)
+ .setCenterX(x)
+ .setCenterY(y).build();
+
+ mService.runOnServiceSync(() -> {
+ controller.setMagnificationConfig(config, false);
+ });
+ waitUntilMagnificationConfig(controller, MAGNIFICATION_MODE_WINDOW, x, y);
+
+ assertEquals(scale, controller.getMagnificationConfig().getScale(), 0);
+
+ mService.runOnServiceSync(() -> {
+ controller.resetCurrentMagnification(false);
+ });
+
+ assertEquals(1.0f, controller.getMagnificationConfig().getScale(), 0);
+ assertTrue(controller.getCurrentMagnificationRegion().isEmpty());
+ }
+
+ @Test
public void testAnimatingMagnification() throws InterruptedException {
final MagnificationController controller = mService.getMagnificationController();
final int timeBetweenAnimationChanges = 100;
@@ -283,17 +677,15 @@
@Test
public void testA11yNodeInfoVisibility_whenOutOfMagnifiedArea_shouldVisible()
throws Exception{
- final UiAutomation uiAutomation = mInstrumentation.getUiAutomation(
- UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
final Activity activity = launchActivityAndWaitForItToBeOnscreen(
- mInstrumentation, uiAutomation, mActivityRule);
+ mInstrumentation, sUiAutomation, mActivityRule);
final MagnificationController controller = mService.getMagnificationController();
final Rect magnifyBounds = controller.getMagnificationRegion().getBounds();
final float scale = 8.0f;
final Button button = activity.findViewById(R.id.button1);
adjustViewBoundsIfNeeded(button, scale, magnifyBounds);
- final AccessibilityNodeInfo buttonNode = uiAutomation.getRootInActiveWindow()
+ final AccessibilityNodeInfo buttonNode = sUiAutomation.getRootInActiveWindow()
.findAccessibilityNodeInfosByViewId(
"android.accessibilityservice.cts:id/button1").get(0);
assertNotNull("Can't find button on the screen", buttonNode);
@@ -330,31 +722,32 @@
private void waitOnMagnificationChanged(MagnificationController controller, float newScale,
float newCenterX, float newCenterY) {
- final Object waitLock = new Object();
- final AtomicBoolean notified = new AtomicBoolean();
- final OnMagnificationChangedListener listener = (c, region, scale, centerX, centerY) -> {
- final float delta = 5.0f;
- synchronized (waitLock) {
- if (newScale == scale
- && (centerX > newCenterX - delta) && (centerY > newCenterY - delta)) {
- notified.set(true);
- waitLock.notifyAll();
- }
- }
- };
+ final OnMagnificationChangedListener spyListener = mock(
+ OnMagnificationChangedListener.class);
+ final OnMagnificationChangedListener listener =
+ (controller1, region, scale, centerX, centerY) ->
+ spyListener.onMagnificationChanged(controller1, region, scale, centerX,
+ centerY);
controller.addListener(listener);
try {
final AtomicBoolean setScale = new AtomicBoolean();
final AtomicBoolean setCenter = new AtomicBoolean();
mService.runOnServiceSync(() -> {
setScale.set(controller.setScale(newScale, false));
+ });
+
+ assertTrue("Failed to set scale", setScale.get());
+ verify(spyListener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
+ eq(controller), any(Region.class), eq(newScale), anyFloat(), anyFloat());
+
+ reset(spyListener);
+ mService.runOnServiceSync(() -> {
setCenter.set(controller.setCenter(newCenterX, newCenterY, false));
});
- assertTrue("Failed to set scale", setScale.get());
- assertEquals("Failed to apply scale", newScale, controller.getScale(), 0f);
+
assertTrue("Failed to set center", setCenter.get());
- waitOn(waitLock, () -> notified.get(), LISTENER_TIMEOUT_MILLIS,
- "waitOnMagnificationChanged");
+ verify(spyListener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
+ eq(controller), any(Region.class), anyFloat(), eq(newCenterX), eq(newCenterY));
} finally {
controller.removeListener(listener);
}
@@ -396,4 +789,25 @@
// Waiting for UI refresh
mInstrumentation.waitForIdleSync();
}
+
+ private void waitUntilMagnificationConfig(MagnificationController controller, int mode,
+ float centerX, float centerY) throws Exception {
+ TestUtils.waitUntil("Failed to apply the config.",
+ () -> {
+ final MagnificationConfig tmpConfig = controller.getMagnificationConfig();
+ return tmpConfig.getMode() == mode && tmpConfig.getCenterX() == centerX
+ && tmpConfig.getCenterY() == centerY;
+ });
+ }
+
+ private void assertConfigEquals(MagnificationConfig expected, MagnificationConfig result) {
+ assertEquals("Failed to apply mode", expected.getMode(),
+ result.getMode(), 0f);
+ assertEquals("Failed to apply scale", expected.getScale(),
+ result.getScale(), 0f);
+ assertEquals("Failed to apply center X", expected.getCenterX(),
+ result.getCenterX(), 5.0f);
+ assertEquals("Failed to apply center Y", expected.getCenterY(),
+ result.getCenterY(), 5.0f);
+ }
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardTest.java
index 5744ac9..5f100b4 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardTest.java
@@ -18,10 +18,13 @@
import static android.accessibilityservice.AccessibilityService.SHOW_MODE_AUTO;
import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD;
+import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_SUCCESS;
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 static org.junit.Assert.fail;
import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
@@ -136,19 +139,74 @@
String currentIME = Settings.Secure.getString(
mService.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
assertNotEquals(Ime1Constants.IME_ID, currentIME);
- // Enable a dummy IME for this test.
- try (TestImeSession imeSession = new TestImeSession(Ime1Constants.IME_ID)) {
- // Switch to the dummy IME.
+ // Enable a placeholder IME for this test.
+ try (TestImeSession imeSession = new TestImeSession(Ime1Constants.IME_ID, true)) {
+ // Switch to the placeholder IME.
final boolean success = controller.switchToInputMethod(Ime1Constants.IME_ID);
currentIME = Settings.Secure.getString(
mService.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
- // The current IME should be set to the dummy IME successfully.
+ // The current IME should be set to the placeholder IME successfully.
assertTrue(success);
assertEquals(Ime1Constants.IME_ID, currentIME);
}
}
+ @Test
+ public void testSetInputMethodEnabled_differentPackage() throws Exception {
+ // Disable a placeholder IME for this test.
+ try (TestImeSession imeSession = new TestImeSession(Ime1Constants.IME_ID, false)) {
+ final SoftKeyboardController controller = mService.getSoftKeyboardController();
+
+ String enabledIMEs = Settings.Secure.getString(
+ mService.getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS);
+ assertFalse(enabledIMEs.contains(Ime1Constants.IME_ID));
+
+ // Enable the placeholder IME.
+ try {
+ int result = controller.setInputMethodEnabled(Ime1Constants.IME_ID, true);
+ fail("should have thrown SecurityException");
+ } catch (SecurityException ignored) {
+ }
+
+ enabledIMEs = Settings.Secure.getString(
+ mService.getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS);
+ // The placeholder IME should not be enabled;
+ assertFalse(enabledIMEs.contains(Ime1Constants.IME_ID));
+ }
+ }
+
+ @Test
+ public void testSetInputMethodEnabled_success() throws Exception {
+ String ImeId = "android.accessibilityservice.cts/.StubInputMethod";
+ // Disable a placeholder IME for this test.
+ try (TestImeSession imeSession = new TestImeSession(ImeId, false)) {
+ final SoftKeyboardController controller = mService.getSoftKeyboardController();
+
+ String enabledIMEs = Settings.Secure.getString(
+ mService.getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS);
+ assertFalse(enabledIMEs.contains(ImeId));
+
+ // Enable the placeholder IME.
+ int result = controller.setInputMethodEnabled(ImeId, true);
+ enabledIMEs = Settings.Secure.getString(
+ mService.getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS);
+
+ // The placeholder IME should be enabled;
+ assertEquals(ENABLE_IME_SUCCESS, result);
+ assertTrue(enabledIMEs.contains(ImeId));
+
+ // Disable the placeholder IME.
+ result = controller.setInputMethodEnabled(ImeId, false);
+ enabledIMEs = Settings.Secure.getString(
+ mService.getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS);
+
+ // The placeholder IME should be disabled;
+ assertEquals(ENABLE_IME_SUCCESS, result);
+ assertFalse(enabledIMEs.contains(ImeId));
+ }
+ }
+
private void assertCanSetAndGetShowModeAndCallbackHappens(
int mode, InstrumentedAccessibilityService service)
throws Exception {
@@ -197,9 +255,14 @@
}
private class TestImeSession implements AutoCloseable {
- TestImeSession(String imeId) {
- // Enable the dummy IME by shell command.
- final String enableImeCommand = ShellCommandUtils.enableIme(imeId);
+ TestImeSession(String imeId, boolean enabled) {
+ // Enable/disable the placeholder IME by shell command.
+ final String enableImeCommand;
+ if (enabled) {
+ enableImeCommand = ShellCommandUtils.enableIme(imeId);
+ } else {
+ enableImeCommand = ShellCommandUtils.disableIme(imeId);
+ }
ShellCommandBuilder.create(mInstrumentation)
.addCommand(enableImeCommand)
.run();
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
index 94791de..4746f01 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
@@ -24,6 +24,7 @@
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen;
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.supportsMultiDisplay;
+import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
import static android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession;
import static android.accessibilityservice.cts.utils.DisplayUtils.getStatusBarHeight;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
@@ -44,15 +45,20 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assume.assumeTrue;
+import android.Manifest;
import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.cts.activities.AccessibilityWindowReportingActivity;
import android.accessibilityservice.cts.activities.NonDefaultDisplayActivity;
+import android.accessibilityservice.cts.activities.NotTouchableWindowTestActivity;
import android.app.Activity;
import android.app.Instrumentation;
import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.Intent;
import android.graphics.Rect;
import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -68,6 +74,8 @@
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.SystemUtil;
+
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -76,6 +84,7 @@
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
+import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeoutException;
@@ -154,10 +163,9 @@
final WindowManager.LayoutParams paramsForBottom = layoutParmsForWindowOnBottom();
final Button button = new Button(mActivity);
button.setText(R.string.button1);
- sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
- () -> mActivity.getWindowManager().addView(button, paramsForTop)),
- filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED),
- TIMEOUT_ASYNC_PROCESSING);
+
+ addWindowAndWaitForEvent(button, paramsForTop,
+ filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED));
// Move window from top to bottom
sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
@@ -410,18 +418,112 @@
assertTrue("Failed to find accessibility window for auto-complete pop-up", foundPopup);
}
+ @AppModeFull
+ @Test
+ public void showNotTouchableWindow_activityWindowIsNotVisible()
+ throws TimeoutException {
+ try {
+ launchNotTouchableWindowTestActivityFromShell();
+
+ Intent intent = new Intent();
+ intent.setAction(NotTouchableWindowTestActivity.ADD_WINDOW);
+
+ try {
+ // Waits for the not-touchable activity is covered by the untrusted window.
+ sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+ () -> sInstrumentation.getContext().sendBroadcast(intent)),
+ (event) -> {
+ final AccessibilityWindowInfo notTouchableWindow =
+ findWindowByTitle(sUiAutomation,
+ NotTouchableWindowTestActivity.TITLE);
+ return notTouchableWindow == null;
+ }, TIMEOUT_ASYNC_PROCESSING);
+ } finally {
+ intent.setAction(NotTouchableWindowTestActivity.REMOVE_WINDOW);
+ sendIntentAndWaitForEvent(intent,
+ filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_REMOVED));
+ }
+ } finally {
+ Intent intent = new Intent();
+ intent.setAction(NotTouchableWindowTestActivity.FINISH_ACTIVITY);
+ sendIntentAndWaitForEvent(intent,
+ filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_REMOVED));
+ }
+ }
+
+ @AppModeFull
+ @Test
+ public void showNotTouchableTrustedWindow_activityWindowIsVisible()
+ throws TimeoutException {
+ try {
+ launchNotTouchableWindowTestActivityFromShell();
+
+ Intent intent = new Intent();
+ intent.setAction(NotTouchableWindowTestActivity.ADD_TRUSTED_WINDOW);
+
+ try {
+ SystemUtil.runWithShellPermissionIdentity(sUiAutomation, () -> {
+ sendIntentAndWaitForEvent(intent,
+ filterWindowsChangeTypesAndWindowTitle(sUiAutomation,
+ WINDOWS_CHANGE_ADDED,
+ NotTouchableWindowTestActivity.NON_TOUCHABLE_WINDOW_TITLE.toString())
+ );
+ }, Manifest.permission.INTERNAL_SYSTEM_WINDOW);
+
+ List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
+ assertNotNull(windows);
+
+ assertEquals(1, windows.stream().filter(
+ w -> NotTouchableWindowTestActivity.TITLE.equals(w.getTitle())).count());
+ } finally {
+ intent.setAction(NotTouchableWindowTestActivity.REMOVE_WINDOW);
+ sendIntentAndWaitForEvent(intent,
+ filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_REMOVED));
+ }
+ } finally {
+ Intent intent = new Intent();
+ intent.setAction(NotTouchableWindowTestActivity.FINISH_ACTIVITY);
+ sendIntentAndWaitForEvent(intent,
+ filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_REMOVED));
+ }
+ }
+
+ // We want to test WindowState#isTrustedOverlay which refers to flag stored in the
+ // Session class and is not updated since the Session is created.
+ // Use shell command instead of ActivityLaunchUtils to get INTERNAL_SYSTEM_WINDOW
+ // permission when the Session is created.
+ private void launchNotTouchableWindowTestActivityFromShell() {
+ SystemUtil.runWithShellPermissionIdentity(sUiAutomation, () -> {
+ sUiAutomation.executeAndWaitForEvent(
+ () -> {
+ final ComponentName componentName = new ComponentName(
+ sInstrumentation.getContext(), NotTouchableWindowTestActivity.class);
+
+ String command = "am start -n " + componentName.flattenToString();
+ try {
+ SystemUtil.runShellCommand(sInstrumentation, command);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ },
+ (event) -> {
+ final AccessibilityWindowInfo window =
+ findWindowByTitleAndDisplay(sUiAutomation,
+ NotTouchableWindowTestActivity.TITLE, 0);
+ return window != null;
+ }, DEFAULT_TIMEOUT_MS);
+ }, Manifest.permission.INTERNAL_SYSTEM_WINDOW);
+ }
+
private View showTopWindowAndWaitForItToShowUp() throws TimeoutException {
final WindowManager.LayoutParams paramsForTop = layoutParmsForWindowOnTop();
final Button button = new Button(mActivity);
button.setText(R.string.button1);
- sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
- () -> mActivity.getWindowManager().addView(button, paramsForTop)),
- (event) -> {
- return (event.getEventType() == TYPE_WINDOWS_CHANGED)
- && (findWindowByTitle(sUiAutomation, mActivityTitle) != null)
- && (findWindowByTitle(sUiAutomation, TOP_WINDOW_TITLE) != null);
- },
- TIMEOUT_ASYNC_PROCESSING);
+ addWindowAndWaitForEvent(button, paramsForTop, (event) -> {
+ return (event.getEventType() == TYPE_WINDOWS_CHANGED)
+ && (findWindowByTitle(sUiAutomation, mActivityTitle) != null)
+ && (findWindowByTitle(sUiAutomation, TOP_WINDOW_TITLE) != null);
+ });
return button;
}
@@ -454,4 +556,22 @@
});
return params;
}
+
+ private void addWindowAndWaitForEvent(View view, WindowManager.LayoutParams params,
+ UiAutomation.AccessibilityEventFilter filter)
+ throws TimeoutException {
+ sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+ () -> mActivity.getWindowManager().addView(view, params)),
+ filter,
+ TIMEOUT_ASYNC_PROCESSING);
+ }
+
+ private void sendIntentAndWaitForEvent(Intent intent,
+ UiAutomation.AccessibilityEventFilter filter) throws TimeoutException {
+ sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+ () -> sInstrumentation.getContext().sendBroadcast(intent)),
+ filter,
+ TIMEOUT_ASYNC_PROCESSING);
+ }
+
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationConfigTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationConfigTest.java
new file mode 100644
index 0000000..49142e8
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationConfigTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
+
+import static org.junit.Assert.assertEquals;
+
+import android.accessibilityservice.MagnificationConfig;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Class for testing {@link android.accessibilityservice.MagnificationConfig}.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MagnificationConfigTest {
+
+ private final int mMode = MAGNIFICATION_MODE_FULLSCREEN;
+ private final float mScale = 1;
+ private final float mCenterX = 2;
+ private final float mCenterY = 3;
+
+ @Test
+ public void testMarshaling() {
+ // Populate the magnification config to marshal.
+ MagnificationConfig magnificationConfig = populateMagnificationConfig();
+
+ // Marshal and unmarshal the magnification config.
+ Parcel parcel = Parcel.obtain();
+ magnificationConfig.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ MagnificationConfig fromParcel =
+ MagnificationConfig.CREATOR.createFromParcel(parcel);
+
+ // Make sure all fields properly marshaled.
+ assertEqualsMagnificationConfig(magnificationConfig, fromParcel);
+
+ parcel.recycle();
+ }
+
+ @Test
+ public void testGetterMethods_dataPopulated_assertDataEqual() {
+ MagnificationConfig magnificationConfig = populateMagnificationConfig();
+
+ assertEquals("getMode is different from magnificationConfig", mMode,
+ magnificationConfig.getMode());
+ assertEquals("getScale is different from magnificationConfig", mScale,
+ magnificationConfig.getScale(), 0);
+ assertEquals("getCenterX is different from magnificationConfig", mCenterX,
+ magnificationConfig.getCenterX(), 0);
+ assertEquals("getCenterY is different from magnificationConfig", mCenterY,
+ magnificationConfig.getCenterY(), 0);
+ }
+
+ private void assertEqualsMagnificationConfig(MagnificationConfig expectedConfig,
+ MagnificationConfig actualConfig) {
+ assertEquals("getMode has incorrect value", expectedConfig.getMode(),
+ actualConfig.getMode());
+ assertEquals("getScale has incorrect value", expectedConfig.getScale(),
+ actualConfig.getScale(), 0);
+ assertEquals("getCenterX has incorrect value", expectedConfig.getCenterX(),
+ actualConfig.getCenterX(), 0);
+ assertEquals("getCenterY has incorrect value", expectedConfig.getCenterY(),
+ actualConfig.getCenterY(), 0);
+ }
+
+ private MagnificationConfig populateMagnificationConfig() {
+ MagnificationConfig.Builder builder =
+ new MagnificationConfig.Builder();
+ MagnificationConfig magnificationConfig = builder.setMode(mMode).setScale(
+ mScale).setCenterX(mCenterX).setCenterY(mCenterY).build();
+ return magnificationConfig;
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubInputMethod.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubInputMethod.java
new file mode 100644
index 0000000..7558758
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubInputMethod.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts;
+
+import android.inputmethodservice.InputMethodService;
+
+public class StubInputMethod extends InputMethodService {
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
index 826e53f..808ad88 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
@@ -16,8 +16,6 @@
import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_END;
import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_START;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
-import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
-import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
import android.view.accessibility.AccessibilityEvent;
@@ -40,8 +38,6 @@
break;
case TYPE_GESTURE_DETECTION_START:
case TYPE_GESTURE_DETECTION_END:
- case TYPE_VIEW_CLICKED:
- case TYPE_VIEW_LONG_CLICKED:
mCollectedEvents.add(event.getEventType());
}
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
index 7ffb23d..26f0907 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
@@ -43,8 +43,6 @@
import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_END;
import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_START;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
-import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
-import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
import static org.hamcrest.CoreMatchers.both;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -53,16 +51,17 @@
import android.accessibility.cts.common.InstrumentedAccessibilityService;
import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
import android.accessibility.cts.common.ShellCommandBuilder;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.GestureDescription.StrokeDescription;
import android.accessibilityservice.cts.AccessibilityGestureDispatchTest.GestureDispatchActivity;
+import android.accessibilityservice.cts.utils.ActivityLaunchUtils;
import android.accessibilityservice.cts.utils.EventCapturingClickListener;
import android.accessibilityservice.cts.utils.EventCapturingHoverListener;
import android.accessibilityservice.cts.utils.EventCapturingLongClickListener;
import android.accessibilityservice.cts.utils.EventCapturingTouchListener;
import android.app.Instrumentation;
import android.app.UiAutomation;
-import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.PointF;
import android.graphics.Region;
@@ -82,7 +81,9 @@
import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
@@ -99,14 +100,17 @@
@AppModeFull
public class TouchExplorerTest {
// Constants
- private static final float GESTURE_LENGTH_MMS = 10.0f;
+ private static final float GESTURE_LENGTH_MMS = 15.0f;
+ private static final float MIN_SCREEN_WIDTH_MM = 40.0f;
+
+ private static String sEnabledServices;
+ private static Instrumentation sInstrumentation;
+ private static UiAutomation sUiAutomation;
+
private TouchExplorationStubAccessibilityService mService;
- private Instrumentation mInstrumentation;
- private UiAutomation mUiAutomation;
private boolean mHasTouchscreen;
private boolean mScreenBigEnough;
private long mSwipeTimeMillis;
- private String mEnabledServices;
private EventCapturingHoverListener mHoverListener = new EventCapturingHoverListener(false);
private EventCapturingTouchListener mTouchListener = new EventCapturingTouchListener(false);
private EventCapturingClickListener mClickListener = new EventCapturingClickListener();
@@ -114,7 +118,7 @@
new EventCapturingLongClickListener();
private ActivityTestRule<GestureDispatchActivity> mActivityRule =
- new ActivityTestRule<>(GestureDispatchActivity.class, false);
+ new ActivityTestRule<>(GestureDispatchActivity.class, false, false);
private InstrumentedAccessibilityServiceTestRule<TouchExplorationStubAccessibilityService>
mServiceRule =
@@ -132,42 +136,55 @@
float mSwipeDistance;
View mView;
- @Before
- public void setUp() throws Exception {
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ @BeforeClass
+ public static void oneTimeSetup() {
+ sInstrumentation = InstrumentationRegistry.getInstrumentation();
// Save enabled accessibility services before disabling them so they can be re-enabled after
// the test.
- mEnabledServices = Settings.Secure.getString(
- mInstrumentation.getContext().getContentResolver(),
+ sEnabledServices = Settings.Secure.getString(
+ sInstrumentation.getContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
// Disable all services before enabling Accessibility service to prevent flakiness
// that depends on which services are enabled.
InstrumentedAccessibilityService.disableAllServices();
- PackageManager pm = mInstrumentation.getContext().getPackageManager();
+ sUiAutomation = sInstrumentation.getUiAutomation(
+ UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+ }
+
+ @AfterClass
+ public static void postTestTearDown() {
+ ShellCommandBuilder.create(sInstrumentation)
+ .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, sEnabledServices)
+ .run();
+ sUiAutomation.destroy();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ ActivityLaunchUtils.homeScreenOrBust(sInstrumentation.getContext(), sUiAutomation);
+
+ mActivityRule.launchActivity(null);
+ PackageManager pm = sInstrumentation.getContext().getPackageManager();
mHasTouchscreen =
pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
|| pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
// Find window size, check that it is big enough for gestures.
// Gestures will start in the center of the window, so we need enough horiz/vert space.
mService = mServiceRule.enableService();
- // To prevent a deadlock, we disable UiAutomation while another a11y service is running.
- mInstrumentation.getUiAutomation(
- UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES).destroy();
mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
- WindowManager windowManager =
- (WindowManager)
- mInstrumentation.getContext().getSystemService(Context.WINDOW_SERVICE);
+ WindowManager windowManager = sInstrumentation.getContext().getSystemService(
+ WindowManager.class);
final DisplayMetrics metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getRealMetrics(metrics);
mScreenBigEnough =
- mView.getWidth() / 2
+ mView.getWidth()
> TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_MM, GESTURE_LENGTH_MMS, metrics);
+ TypedValue.COMPLEX_UNIT_MM, MIN_SCREEN_WIDTH_MM, metrics);
if (!mHasTouchscreen || !mScreenBigEnough) return;
mView.setOnHoverListener(mHoverListener);
mView.setOnTouchListener(mTouchListener);
- mInstrumentation.runOnMainSync(
+ sInstrumentation.runOnMainSync(
() -> {
int[] viewLocation = new int[2];
mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
@@ -175,8 +192,9 @@
final int midY = mView.getHeight() / 2;
mView.getLocationOnScreen(viewLocation);
mTapLocation = new PointF(viewLocation[0] + midX, viewLocation[1] + midY);
- mSwipeDistance = mView.getWidth() / 4;
-
+ mSwipeDistance =
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_MM, GESTURE_LENGTH_MMS, metrics);
// This must be slower than 10mm per 150ms to be detected as touch exploration.
final double swipeDistanceMm = mSwipeDistance / metrics.xdpi * 25.4;
mSwipeTimeMillis = (long) swipeDistanceMm * 20;
@@ -186,14 +204,6 @@
});
}
- @After
- public void postTestTearDown() {
- ShellCommandBuilder.create(mInstrumentation)
- .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, mEnabledServices)
- .run();
-
- }
-
/** Test a slow swipe which should initiate touch exploration. */
@Test
@AppModeFull
@@ -282,7 +292,7 @@
public void testSloppyDoubleTapAccessibilityFocus_performsClick() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
syncAccessibilityFocusToInputFocus();
- int slop = ViewConfiguration.get(mInstrumentation.getContext()).getScaledDoubleTapSlop();
+ int slop = ViewConfiguration.get(sInstrumentation.getContext()).getScaledDoubleTapSlop();
dispatch(multiTap(mTapLocation, 2, slop));
mHoverListener.assertNonePropagated();
// The click should not be delivered via touch events in this case.
@@ -290,8 +300,7 @@
mService.assertPropagated(
TYPE_VIEW_ACCESSIBILITY_FOCUSED,
TYPE_TOUCH_INTERACTION_START,
- TYPE_TOUCH_INTERACTION_END,
- TYPE_VIEW_CLICKED);
+ TYPE_TOUCH_INTERACTION_END);
mClickListener.assertClicked(mView);
}
@@ -313,8 +322,7 @@
mService.assertPropagated(
TYPE_VIEW_ACCESSIBILITY_FOCUSED,
TYPE_TOUCH_INTERACTION_START,
- TYPE_TOUCH_INTERACTION_END,
- TYPE_VIEW_CLICKED);
+ TYPE_TOUCH_INTERACTION_END);
mClickListener.assertClicked(mView);
}
@@ -378,8 +386,7 @@
TYPE_TOUCH_INTERACTION_START,
TYPE_TOUCH_EXPLORATION_GESTURE_START,
TYPE_TOUCH_EXPLORATION_GESTURE_END,
- TYPE_TOUCH_INTERACTION_END,
- TYPE_VIEW_CLICKED);
+ TYPE_TOUCH_INTERACTION_END);
mClickListener.assertClicked(mView);
}
@@ -391,6 +398,7 @@
@AppModeFull
public void testDoubleTapNoAccessibilityFocus_sendsTouchEvents() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
+
// Do a single tap so there is a valid last touch-explored location.
dispatch(click(mTapLocation));
mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
@@ -407,7 +415,7 @@
// The click gets delivered as a series of touch events.
mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
mService.assertPropagated(
- TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END, TYPE_VIEW_CLICKED);
+ TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
mClickListener.assertClicked(mView);
}
@@ -436,7 +444,7 @@
// The click gets delivered as a series of touch events.
mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
mService.assertPropagated(
- TYPE_TOUCH_INTERACTION_START, TYPE_VIEW_LONG_CLICKED, TYPE_TOUCH_INTERACTION_END);
+ TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
mLongClickListener.assertLongClicked(mView);
}
@@ -463,8 +471,7 @@
mService.assertPropagated(
TYPE_VIEW_ACCESSIBILITY_FOCUSED,
TYPE_TOUCH_INTERACTION_START,
- TYPE_TOUCH_INTERACTION_END,
- TYPE_VIEW_CLICKED);
+ TYPE_TOUCH_INTERACTION_END);
mClickListener.assertClicked(mView);
}
@@ -578,14 +585,14 @@
mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
// We still want accessibility events to tell us when the gesture starts and ends.
mService.assertPropagated(
- TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END, TYPE_VIEW_CLICKED);
+ TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
mService.clearEvents();
// Swipe starting inside the passthrough region but ending outside of it. This should still
// behave as a passthrough interaction.
dispatch(swipe(mTapLocation, add(mTapLocation, -mSwipeDistance, 0)));
mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
mService.assertPropagated(
- TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END, TYPE_VIEW_CLICKED);
+ TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
mService.clearEvents();
// Swipe outside the passthrough region. This should not generate touch events.
dispatch(swipe(add(mTapLocation, -1, 0), add(mTapLocation, -mSwipeDistance, 0)));
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchInteractionControllerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchInteractionControllerTest.java
new file mode 100644
index 0000000..9defc25
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchInteractionControllerTest.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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.doubleTap;
+import static android.accessibilityservice.cts.utils.GestureUtils.swipe;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_HOVER_ENTER;
+import static android.view.MotionEvent.ACTION_HOVER_EXIT;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
+import static android.view.MotionEvent.ACTION_UP;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_START;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
+import android.accessibility.cts.common.ShellCommandBuilder;
+import android.accessibilityservice.GestureDescription;
+import android.accessibilityservice.GestureDescription.StrokeDescription;
+import android.accessibilityservice.TouchInteractionController;
+import android.accessibilityservice.cts.AccessibilityGestureDispatchTest.GestureDispatchActivity;
+import android.accessibilityservice.cts.utils.ActivityLaunchUtils;
+import android.accessibilityservice.cts.utils.EventCapturingClickListener;
+import android.accessibilityservice.cts.utils.EventCapturingHoverListener;
+import android.accessibilityservice.cts.utils.EventCapturingLongClickListener;
+import android.accessibilityservice.cts.utils.EventCapturingTouchListener;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.pm.PackageManager;
+import android.graphics.PointF;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executors;
+
+/**
+ * A set of tests for testing touch exploration. Each test dispatches a gesture and checks for the
+ * appropriate hover and/or touch events followed by the appropriate accessibility events. Some
+ * tests will then check for events from the view.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+@Presubmit
+public class TouchInteractionControllerTest {
+ // Constants
+ private static final float GESTURE_LENGTH_MM = 15.0f;
+ private static final float MIN_SCREEN_WIDTH_MM = 40.0f;
+
+ private static String sEnabledServices;
+ private static Instrumentation sInstrumentation;
+ private static UiAutomation sUiAutomation;
+
+ private TouchExplorationStubAccessibilityService mService;
+ private boolean mHasTouchscreen;
+ private boolean mScreenBigEnough;
+ private long mSwipeTimeMillis;
+ private EventCapturingHoverListener mHoverListener = new EventCapturingHoverListener(false);
+ private EventCapturingTouchListener mTouchListener = new EventCapturingTouchListener(false);
+ private EventCapturingClickListener mClickListener = new EventCapturingClickListener();
+ private EventCapturingLongClickListener mLongClickListener =
+ new EventCapturingLongClickListener();
+
+ private ActivityTestRule<GestureDispatchActivity> mActivityRule =
+ new ActivityTestRule<>(GestureDispatchActivity.class, false, false);
+
+ private InstrumentedAccessibilityServiceTestRule<TouchExplorationStubAccessibilityService>
+ mServiceRule =
+ new InstrumentedAccessibilityServiceTestRule<>(
+ TouchExplorationStubAccessibilityService.class, false);
+
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain =
+ RuleChain.outerRule(mActivityRule).around(mServiceRule).around(mDumpOnFailureRule);
+
+ PointF mTapLocation; // Center of activity. Gestures all start from around this point.
+ float mSwipeDistance;
+ View mView;
+ TouchInteractionController mController;
+
+ @BeforeClass
+ public static void oneTimeSetup() {
+ sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ // Save enabled accessibility services before disabling them so they can be re-enabled after
+ // the test.
+ sEnabledServices =
+ Settings.Secure.getString(
+ sInstrumentation.getContext().getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+ // Disable all services before enabling Accessibility service to prevent flakiness
+ // that depends on which services are enabled.
+ InstrumentedAccessibilityService.disableAllServices();
+ sUiAutomation =
+ sInstrumentation.getUiAutomation(
+ UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+ }
+
+ @AfterClass
+ public static void postTestTearDown() {
+ ShellCommandBuilder.create(sInstrumentation)
+ .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, sEnabledServices)
+ .run();
+ sUiAutomation.destroy();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ ActivityLaunchUtils.homeScreenOrBust(sInstrumentation.getContext(), sUiAutomation);
+
+ mActivityRule.launchActivity(null);
+ PackageManager pm = sInstrumentation.getContext().getPackageManager();
+ mHasTouchscreen =
+ pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
+ || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
+ // Find window size, check that it is big enough for gestures.
+ // Gestures will start in the center of the window, so we need enough horiz/vert space.
+ mService = mServiceRule.enableService();
+ mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
+ WindowManager windowManager =
+ sInstrumentation.getContext().getSystemService(WindowManager.class);
+ final DisplayMetrics metrics = new DisplayMetrics();
+ windowManager.getDefaultDisplay().getRealMetrics(metrics);
+ mScreenBigEnough =
+ mView.getWidth()
+ > TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_MM, MIN_SCREEN_WIDTH_MM, metrics);
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+
+ mView.setOnHoverListener(mHoverListener);
+ mView.setOnTouchListener(mTouchListener);
+ sInstrumentation.runOnMainSync(
+ () -> {
+ int[] viewLocation = new int[2];
+ mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
+ final int midX = mView.getWidth() / 2;
+ final int midY = mView.getHeight() / 2;
+ mView.getLocationOnScreen(viewLocation);
+ mTapLocation = new PointF(viewLocation[0] + midX, viewLocation[1] + midY);
+ mSwipeDistance =
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_MM, GESTURE_LENGTH_MM, metrics);
+ // This must be slower than 10mm per 150ms to be detected as touch exploration.
+ final double swipeDistanceMm = mSwipeDistance / metrics.xdpi * 25.4;
+ mSwipeTimeMillis = (long) swipeDistanceMm * 20;
+
+ mView.setOnClickListener(mClickListener);
+ mView.setOnLongClickListener(mLongClickListener);
+ });
+ mController = mService.getTouchInteractionController(Display.DEFAULT_DISPLAY);
+ }
+
+ @After
+ public void tearDown() {
+ mService.disableSelfAndRemove();
+ }
+
+ public void assertBasicConsistency() {
+ assertEquals(Display.DEFAULT_DISPLAY, mController.getDisplayId());
+ assertTrue(mController.getMaxPointerCount() > 0);
+ int state = mController.getState();
+ assertNotEquals("Unknown state: " + state, TouchInteractionController.stateToString(state));
+ }
+
+ /** Test whether we can initiate touch exploration when performing a single tap. */
+ @Test
+ @AppModeFull
+ public void testSingleTap_initiatesTouchExploration() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ assertBasicConsistency();
+ mController.registerCallback(
+ Executors.newSingleThreadExecutor(),
+ new BaseCallback() {
+ public void onMotionEvent(MotionEvent event) {
+ if (event.getActionMasked() == ACTION_DOWN) {
+ mController.requestTouchExploration();
+ }
+ }
+ });
+ dispatch(click(mTapLocation));
+ mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
+ mTouchListener.assertNonePropagated();
+ }
+
+ /** Test whether we can initiate a drag. */
+ @Test
+ @AppModeFull
+ public void testTwoFingerDrag_sendsTouchEvents() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ assertBasicConsistency();
+ mController.registerCallback(
+ Executors.newSingleThreadExecutor(),
+ new BaseCallback() {
+ public void onMotionEvent(MotionEvent event) {
+ if (event.getActionMasked() == ACTION_POINTER_DOWN) {
+ mController.requestDragging(event.getPointerId(0));
+ }
+ }
+ });
+ // A two point moving that are in the same direction can perform a drag gesture by
+ // TouchExplorer while one point moving can not perform a drag gesture. We use two
+ // swipes
+ // to emulate a two finger drag gesture.
+ final int twoFingerOffset = (int) mSwipeDistance;
+ final PointF dragStart = mTapLocation;
+ final PointF dragEnd = add(dragStart, 0, mSwipeDistance);
+ final PointF finger1Start = add(dragStart, twoFingerOffset, 0);
+ final PointF finger1End = add(finger1Start, 0, mSwipeDistance);
+ final PointF finger2Start = add(dragStart, -twoFingerOffset, 0);
+ final PointF finger2End = add(finger2Start, 0, mSwipeDistance);
+ dispatch(
+ swipe(finger1Start, finger1End, mSwipeTimeMillis),
+ swipe(finger2Start, finger2End, mSwipeTimeMillis));
+ mHoverListener.assertNonePropagated();
+ mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
+ }
+
+ /**
+ * This method tests the case where two fingers are moving independently. The gesture should be
+ * delegated to the view as-is. This is distinct from dragging, where two fingers are delegated
+ * to the view as one finger.
+ */
+ @Test
+ @AppModeFull
+ public void testTwoFingersMovingIndependently_shouldDelegate() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ assertBasicConsistency();
+ mController.registerCallback(
+ Executors.newSingleThreadExecutor(),
+ new BaseCallback() {
+ public void onMotionEvent(MotionEvent event) {
+ if (event.getActionMasked() == ACTION_POINTER_DOWN) {
+ mController.requestDelegating();
+ }
+ }
+ });
+ // Move two fingers towards eacher slowly.
+ PointF finger1Start = add(mTapLocation, -mSwipeDistance, 0);
+ PointF finger1End = add(mTapLocation, -10, 0);
+ StrokeDescription swipe1 = swipe(finger1Start, finger1End, mSwipeTimeMillis);
+ PointF finger2Start = add(mTapLocation, mSwipeDistance, 0);
+ PointF finger2End = add(mTapLocation, 10, 0);
+ StrokeDescription swipe2 = swipe(finger2Start, finger2End, mSwipeTimeMillis);
+ dispatch(swipe1, swipe2);
+ mHoverListener.assertNonePropagated();
+ mTouchListener.assertPropagated(
+ ACTION_DOWN, ACTION_POINTER_DOWN, ACTION_MOVE, ACTION_POINTER_UP, ACTION_UP);
+ }
+
+ /** Insure that double-tap is recognized as a single interaction. */
+ @Test
+ @AppModeFull
+ public void testDoubleTap_producesSingleInteraction() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ assertBasicConsistency();
+ dispatch(doubleTap(mTapLocation));
+ mService.assertPropagated(TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
+ }
+
+ /**
+ * Test the case where we want to click on the item that has accessibility focus by using
+ * AccessibilityNodeInfo.performAction. Note that this test does not request that double tap be
+ * dispatched to the accessibility service, meaning that it will be handled by the framework and
+ * the view will be clicked.
+ */
+ @Test
+ @AppModeFull
+ public void testPerformClickAccessibilityFocus_performsClick() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ assertBasicConsistency();
+ syncAccessibilityFocusToInputFocus();
+ mController.performClick();
+ mService.assertPropagated(TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+ mHoverListener.assertNonePropagated();
+ // The click should not be delivered via touch events in this case.
+ mTouchListener.assertNonePropagated();
+ mClickListener.assertClicked(mView);
+ }
+
+ /**
+ * Test the case where we double tap but there is no accessibility focus. Nothing should happen.
+ */
+ @Test
+ @AppModeFull
+ public void testPerformClickNoFocus_doesNotPerformClick() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ assertBasicConsistency();
+ mController.performClick();
+ mHoverListener.assertNonePropagated();
+ mTouchListener.assertNonePropagated();
+ mClickListener.assertNoneClicked();
+ }
+
+ /** Set the accessibility focus to the element that has input focus. */
+ @Test
+ @AppModeFull
+ public void testPerformLongClick_sendsMotionEvents() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ assertBasicConsistency();
+ // First perform touch exploration.
+ mController.registerCallback(
+ Executors.newSingleThreadExecutor(),
+ new BaseCallback() {
+ public void onMotionEvent(MotionEvent event) {
+ if (event.getActionMasked() == ACTION_DOWN) {
+ mController.requestTouchExploration();
+ }
+ }
+ });
+ dispatch(click(mTapLocation));
+ mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
+ mTouchListener.assertNonePropagated();
+ // Wait for the interaction ends before beginning a new one.
+ mService.assertPropagated(
+ TYPE_TOUCH_INTERACTION_START,
+ TYPE_TOUCH_EXPLORATION_GESTURE_START,
+ TYPE_TOUCH_EXPLORATION_GESTURE_END,
+ TYPE_TOUCH_INTERACTION_END);
+ mController.unregisterAllCallbacks();
+ mController.registerCallback(
+ Executors.newSingleThreadExecutor(),
+ new BaseCallback() {
+ public void onMotionEvent(MotionEvent event) {
+ if (event.getActionMasked() == ACTION_DOWN) {
+ mController.performLongClickAndStartDrag();
+ }
+ }
+ });
+ PointF endPoint = add(mTapLocation, mSwipeDistance, 0);
+ dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0), mSwipeTimeMillis));
+ mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
+ }
+
+ @Test
+ @AppModeFull
+ public void testRemove_shouldReturnControlToFramework() {
+ if (!mHasTouchscreen || !mScreenBigEnough) return;
+ assertBasicConsistency();
+ TouchInteractionController.Callback callback = new BaseCallback();
+ mController.registerCallback(Executors.newSingleThreadExecutor(), callback);
+ dispatch(click(mTapLocation));
+ // Nothing should happen because the callback is empty.
+ mTouchListener.assertNonePropagated();
+ mController.unregisterCallback(callback);
+ mHoverListener.assertNonePropagated();
+ dispatch(click(mTapLocation));
+ mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
+ mTouchListener.assertNonePropagated();
+ }
+
+ private void syncAccessibilityFocusToInputFocus() {
+ mService.runOnServiceSync(
+ () -> {
+ AccessibilityNodeInfo focus =
+ mService.findFocus(AccessibilityNodeInfo.FOCUS_INPUT);
+ focus.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ focus.recycle();
+ });
+ mService.waitForAccessibilityFocus();
+ }
+
+ 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));
+ }
+
+ class BaseCallback implements TouchInteractionController.Callback {
+
+ @Override
+ public void onMotionEvent(MotionEvent event) {}
+
+ @Override
+ public void onStateChanged(int state) {}
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityCacheActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityCacheActivity.java
new file mode 100644
index 0000000..f66a6fc
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityCacheActivity.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts.activities;
+
+import android.accessibilityservice.cts.R;
+import android.os.Bundle;
+
+public class AccessibilityCacheActivity extends AccessibilityTestActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.accessibility_cache);
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/NotTouchableWindowTestActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/NotTouchableWindowTestActivity.java
new file mode 100644
index 0000000..f4ec5b8
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/NotTouchableWindowTestActivity.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts.activities;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+
+public class NotTouchableWindowTestActivity extends AccessibilityTestActivity {
+ private class CommandReceiver extends BroadcastReceiver {
+ private View rootView;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ WindowManager.LayoutParams params;
+ switch (intent.getAction()) {
+ case ADD_WINDOW:
+ if (rootView != null) {
+ throw new IllegalStateException("Window already exists");
+ }
+ rootView = new View(context);
+ params = createDefaultWindowParams();
+ context.getSystemService(WindowManager.class).addView(rootView, params);
+ break;
+
+ case REMOVE_WINDOW:
+ context.getSystemService(WindowManager.class).removeViewImmediate(rootView);
+ rootView = null;
+ break;
+
+ case ADD_TRUSTED_WINDOW:
+ if (rootView != null) {
+ throw new IllegalStateException("Window already exists");
+ }
+ rootView = new Button(context);
+ params = createDefaultWindowParams();
+ params.privateFlags |= PRIVATE_FLAG_TRUSTED_OVERLAY;
+ context.getSystemService(WindowManager.class).addView(rootView, params);
+ break;
+
+ case FINISH_ACTIVITY:
+ if (rootView != null) {
+ throw new IllegalStateException("Window still exists");
+ }
+ finish();
+ }
+ }
+
+ }
+
+ private BroadcastReceiver mBroadcastReceiver = new CommandReceiver();
+
+ public static final CharSequence TITLE =
+ "NotTouchableWindowTestActivity";
+ public static final CharSequence NON_TOUCHABLE_WINDOW_TITLE =
+ "android.accessibilityservice.cts.activities.NON_TOUCHABLE_WINDOW_TITLE";
+
+ public static final String ADD_WINDOW =
+ "android.accessibilityservice.cts.ADD_WINDOW";
+ public static final String REMOVE_WINDOW =
+ "android.accessibilityservice.cts.REMOVE_WINDOW";
+ public static final String ADD_TRUSTED_WINDOW =
+ "android.accessibilityservice.cts.ADD_TRUSTED_WINDOW";
+ public static final String FINISH_ACTIVITY =
+ "android.accessibilityservice.cts.FINISH_ACTIVITY";
+
+ // From WindowManager.java.
+ public static final int PRIVATE_FLAG_TRUSTED_OVERLAY = 0x20000000;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
+ filter.addAction(ADD_WINDOW);
+ filter.addAction(REMOVE_WINDOW);
+ filter.addAction(ADD_TRUSTED_WINDOW);
+ filter.addAction(FINISH_ACTIVITY);
+ this.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
+
+ setTitle(TITLE);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ this.unregisterReceiver(mBroadcastReceiver);
+ }
+
+ private static WindowManager.LayoutParams createDefaultWindowParams() {
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.height = WindowManager.LayoutParams.MATCH_PARENT;
+ params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+
+ params.gravity = Gravity.TOP;
+ params.setTitle(NON_TOUCHABLE_WINDOW_TITLE);
+
+ return params;
+ }
+}
\ No newline at end of file
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
index 41c5bad..7e9904c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
@@ -63,7 +63,7 @@
private static final String LOG_TAG = "ActivityLaunchUtils";
private static final String AM_START_HOME_ACTIVITY_COMMAND =
"am start -a android.intent.action.MAIN -c android.intent.category.HOME";
- public static final String AM_BROADCAST_CLOSE_SYSTEM_DIALOG_COMMAND =
+ private static final String AM_BROADCAST_CLOSE_SYSTEM_DIALOG_COMMAND =
"am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS";
// Using a static variable so it can be used in lambdas. Not preserving state in it.
@@ -135,6 +135,11 @@
wakeUpOrBust(context, uiAutomation);
if (context.getPackageManager().isInstantApp()) return;
if (isHomeScreenShowing(context, uiAutomation)) return;
+ final AccessibilityServiceInfo serviceInfo = uiAutomation.getServiceInfo();
+ final int enabledFlags = serviceInfo.flags;
+ // Make sure we could query windows.
+ serviceInfo.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ uiAutomation.setServiceInfo(serviceInfo);
try {
executeAndWaitOn(
uiAutomation,
@@ -159,6 +164,9 @@
}
fail("Unable to reach home screen");
+ } finally {
+ serviceInfo.flags = enabledFlags;
+ uiAutomation.setServiceInfo(serviceInfo);
}
}
@@ -182,7 +190,8 @@
if (packageName != null) {
for (ResolveInfo resolveInfo : resolveInfos) {
if ((resolveInfo.activityInfo != null)
- && packageName.equals(resolveInfo.activityInfo.packageName)) {
+ && packageName.equals(resolveInfo.activityInfo.packageName)
+ && window.isActive()) {
return true;
}
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java
index 87d46ba..c0ce0d2 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java
@@ -17,14 +17,17 @@
package android.accessibilityservice.cts.utils;
import static android.view.MotionEvent.ACTION_HOVER_MOVE;
-import static java.util.concurrent.TimeUnit.SECONDS;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
import android.view.MotionEvent;
import android.view.View;
+
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
@@ -95,6 +98,9 @@
received.add(MotionEvent.actionToString(action));
}
}
+ if (expected.size() == received.size()) {
+ break;
+ }
ev = mEvents.poll(waitTime, SECONDS);
}
assertEquals(expected, received);
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
index 3997ebe..d435d4c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
@@ -18,14 +18,14 @@
import static android.view.MotionEvent.ACTION_MOVE;
-import static java.util.concurrent.TimeUnit.SECONDS;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
import android.view.MotionEvent;
import android.view.View;
@@ -98,6 +98,9 @@
received.add(MotionEvent.actionToString(action));
}
}
+ if (expected.size() == received.size()) {
+ break;
+ }
ev = events.poll(WAIT_TIME_SECONDS, SECONDS);
}
assertEquals(expected, received);
diff --git a/tests/app/AndroidManifest.xml b/tests/app/AndroidManifest.xml
index 3e1eb6a..96e92ab 100644
--- a/tests/app/AndroidManifest.xml
+++ b/tests/app/AndroidManifest.xml
@@ -25,9 +25,11 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
+ <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
<uses-permission android:name="android.permission.READ_PROJECTION_STATE" />
<uses-permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner" />
diff --git a/tests/app/AndroidTest.xml b/tests/app/AndroidTest.xml
index b43053c..e8857cc 100644
--- a/tests/app/AndroidTest.xml
+++ b/tests/app/AndroidTest.xml
@@ -33,7 +33,7 @@
<option name="test-file-name" value="CtsBadProviderStubs.apk" />
<option name="test-file-name" value="CtsCantSaveState1.apk" />
<option name="test-file-name" value="CtsCantSaveState2.apk" />
- <option name="test-file-name" value="NotificationDelegator.apk" />
+ <option name="test-file-name" value="NotificationApp.apk" />
<option name="test-file-name" value="NotificationProvider.apk" />
<option name="test-file-name" value="NotificationListener.apk" />
<option name="test-file-name" value="StorageDelegator.apk" />
diff --git a/tests/app/NotificationApp/Android.bp b/tests/app/NotificationApp/Android.bp
new file mode 100644
index 0000000..c5deb32
--- /dev/null
+++ b/tests/app/NotificationApp/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "NotificationApp",
+ defaults: ["cts_support_defaults"],
+ srcs: ["**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ ],
+ sdk_version: "current",
+}
diff --git a/tests/app/NotificationApp/AndroidManifest.xml b/tests/app/NotificationApp/AndroidManifest.xml
new file mode 100644
index 0000000..144a1bb
--- /dev/null
+++ b/tests/app/NotificationApp/AndroidManifest.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.notificationapp">
+ <!-- for calling matchesCallFilter -->
+ <uses-permission android:name="android.permission.READ_CONTACTS"/>
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
+
+ <application android:label="Notification Test App">
+ <activity android:name="com.android.test.notificationapp.NotificationDelegator"
+ 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>
+
+ <activity android:name="com.android.test.notificationapp.NotificationRevoker"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+
+ <activity android:name="com.android.test.notificationapp.NotificationDelegateAndPost"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name="com.android.test.notificationapp.MatchesCallFilterTestActivity"
+ android:excludeFromRecents="true"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+
+ <service android:name="com.android.test.notificationapp.TestNotificationListener"
+ android:exported="true"
+ android:label="TestNotificationListener"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService"/>
+ </intent-filter>
+ </service>
+
+ </application>
+</manifest>
diff --git a/tests/app/NotificationDelegator/OWNERS b/tests/app/NotificationApp/OWNERS
similarity index 100%
rename from tests/app/NotificationDelegator/OWNERS
rename to tests/app/NotificationApp/OWNERS
diff --git a/tests/app/NotificationDelegator/res/layout/activity.xml b/tests/app/NotificationApp/res/layout/activity.xml
similarity index 100%
rename from tests/app/NotificationDelegator/res/layout/activity.xml
rename to tests/app/NotificationApp/res/layout/activity.xml
diff --git a/tests/app/NotificationApp/src/com/android/test/notificationapp/MatchesCallFilterTestActivity.java b/tests/app/NotificationApp/src/com/android/test/notificationapp/MatchesCallFilterTestActivity.java
new file mode 100644
index 0000000..2b7a4b1
--- /dev/null
+++ b/tests/app/NotificationApp/src/com/android/test/notificationapp/MatchesCallFilterTestActivity.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.notificationapp;
+
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+
+/**
+ * An activity that's only meant to determine whether NotificationManager#matchesCallFilter
+ * was allowed to run, for permission-checking reasons.
+ * It is not used for functionality tests, as it will only call matchesCallFilter on a
+ * meaningless uri.
+ */
+public class MatchesCallFilterTestActivity extends Activity {
+ private NotificationManager mNotificationManager;
+ private Uri mCallDest;
+
+ // result codes for return
+ private static int sNotPermitted = 0;
+ private static int sPermitted = 1;
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ mNotificationManager = getSystemService(NotificationManager.class);
+ mCallDest = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode("+16175551212"));
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ callMatchesCallFilter();
+ finish();
+ }
+
+ private void callMatchesCallFilter() {
+ try {
+ mNotificationManager.matchesCallFilter(mCallDest);
+ setResult(sPermitted);
+ } catch (SecurityException e) {
+ setResult(sNotPermitted);
+ }
+ }
+}
diff --git a/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationDelegateAndPost.java b/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationDelegateAndPost.java
new file mode 100644
index 0000000..4d52bd9
--- /dev/null
+++ b/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationDelegateAndPost.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.notificationapp;
+
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.os.Bundle;
+import android.util.Log;
+
+public class NotificationDelegateAndPost extends Activity {
+ private static final String TAG = "DelegateAndPost";
+ private static final String DELEGATE = "android.app.stubs";
+ private static final String CHANNEL = "channel";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity);
+
+ NotificationManager nm = getSystemService(NotificationManager.class);
+
+ nm.createNotificationChannel(new NotificationChannel(CHANNEL, CHANNEL, IMPORTANCE_LOW));
+ nm.setNotificationDelegate(DELEGATE);
+ Log.d(TAG, "Set delegate: " + nm.getNotificationDelegate());
+
+ Log.d(TAG, "Posting notification with id 9");
+
+ Notification n = new Notification.Builder(this, CHANNEL)
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentTitle("posted by delegator")
+ .build();
+
+ nm.notify(9, n);
+
+ finish();
+ }
+}
diff --git a/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationDelegator.java b/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationDelegator.java
new file mode 100644
index 0000000..9745827
--- /dev/null
+++ b/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationDelegator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.notificationapp;
+
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+
+import android.app.Activity;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.os.Bundle;
+import android.util.Log;
+
+public class NotificationDelegator extends Activity {
+ private static final String TAG = "Delegator";
+ private static final String DELEGATE = "android.app.stubs";
+ private static final String CHANNEL = "channel";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity);
+
+ NotificationManager nm = getSystemService(NotificationManager.class);
+
+ nm.createNotificationChannel(new NotificationChannel(CHANNEL, CHANNEL, IMPORTANCE_LOW));
+ nm.setNotificationDelegate(DELEGATE);
+ Log.d(TAG, "Set delegate: " + nm.getNotificationDelegate());
+ finish();
+ }
+}
diff --git a/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationRevoker.java b/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationRevoker.java
new file mode 100644
index 0000000..54c6c8d
--- /dev/null
+++ b/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationRevoker.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.notificationapp;
+
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.os.Bundle;
+import android.util.Log;
+
+public class NotificationRevoker extends Activity {
+ private static final String TAG = "Revoker";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity);
+
+ NotificationManager nm = getSystemService(NotificationManager.class);
+ nm.setNotificationDelegate(null);
+ Log.d(TAG, "Removed delegate: " + nm.getNotificationDelegate());
+ nm.cancelAll();
+ finish();
+ }
+}
diff --git a/tests/app/NotificationApp/src/com/android/test/notificationapp/TestNotificationListener.java b/tests/app/NotificationApp/src/com/android/test/notificationapp/TestNotificationListener.java
new file mode 100644
index 0000000..b294e48
--- /dev/null
+++ b/tests/app/NotificationApp/src/com/android/test/notificationapp/TestNotificationListener.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test.notificationapp;
+
+import android.service.notification.NotificationListenerService;
+
+// Minimal possible NotificationListenerService, used just to grant notification listener access.
+public class TestNotificationListener extends NotificationListenerService {
+ public static final String TAG = "TestNotificationListener";
+ public static final String PKG = "com.android.test.notificationapp";
+}
diff --git a/tests/app/NotificationDelegator/Android.bp b/tests/app/NotificationDelegator/Android.bp
deleted file mode 100644
index f4fbdd3..0000000
--- a/tests/app/NotificationDelegator/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
- name: "NotificationDelegator",
- defaults: ["cts_support_defaults"],
- srcs: ["**/*.java"],
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
- "sts",
- ],
- sdk_version: "current",
-}
diff --git a/tests/app/NotificationDelegator/AndroidManifest.xml b/tests/app/NotificationDelegator/AndroidManifest.xml
deleted file mode 100644
index a05dcd2..0000000
--- a/tests/app/NotificationDelegator/AndroidManifest.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.test.notificationdelegator">
- <application android:label="Notification Delegator">
- <activity android:name="com.android.test.notificationdelegator.NotificationDelegator"
- 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>
-
- <activity android:name="com.android.test.notificationdelegator.NotificationRevoker"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- </activity>
-
- <activity android:name="com.android.test.notificationdelegator.NotificationDelegateAndPost"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- </activity>
-
- </application>
-</manifest>
diff --git a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegateAndPost.java b/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegateAndPost.java
deleted file mode 100644
index 521adc5..0000000
--- a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegateAndPost.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.test.notificationdelegator;
-
-import static android.app.NotificationManager.IMPORTANCE_LOW;
-
-import android.app.Activity;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.os.Bundle;
-import android.util.Log;
-
-public class NotificationDelegateAndPost extends Activity {
- private static final String TAG = "DelegateAndPost";
- private static final String DELEGATE = "android.app.stubs";
- private static final String CHANNEL = "channel";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity);
-
- NotificationManager nm = getSystemService(NotificationManager.class);
-
- nm.createNotificationChannel(new NotificationChannel(CHANNEL, CHANNEL, IMPORTANCE_LOW));
- nm.setNotificationDelegate(DELEGATE);
- Log.d(TAG, "Set delegate: " + nm.getNotificationDelegate());
-
- Log.d(TAG, "Posting notification with id 9");
-
- Notification n = new Notification.Builder(this, CHANNEL)
- .setSmallIcon(android.R.drawable.sym_def_app_icon)
- .setContentTitle("posted by delegator")
- .build();
-
- nm.notify(9, n);
-
- finish();
- }
-}
diff --git a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegator.java b/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegator.java
deleted file mode 100644
index 15cf42d..0000000
--- a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegator.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.test.notificationdelegator;
-
-import static android.app.NotificationManager.IMPORTANCE_LOW;
-
-import android.app.Activity;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.os.Bundle;
-import android.util.Log;
-
-public class NotificationDelegator extends Activity {
- private static final String TAG = "Delegator";
- private static final String DELEGATE = "android.app.stubs";
- private static final String CHANNEL = "channel";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity);
-
- NotificationManager nm = getSystemService(NotificationManager.class);
-
- nm.createNotificationChannel(new NotificationChannel(CHANNEL, CHANNEL, IMPORTANCE_LOW));
- nm.setNotificationDelegate(DELEGATE);
- Log.d(TAG, "Set delegate: " + nm.getNotificationDelegate());
- finish();
- }
-}
diff --git a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationRevoker.java b/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationRevoker.java
deleted file mode 100644
index 750549a..0000000
--- a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationRevoker.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.test.notificationdelegator;
-
-import android.app.Activity;
-import android.app.NotificationManager;
-import android.os.Bundle;
-import android.util.Log;
-
-public class NotificationRevoker extends Activity {
- private static final String TAG = "Revoker";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity);
-
- NotificationManager nm = getSystemService(NotificationManager.class);
- nm.setNotificationDelegate(null);
- Log.d(TAG, "Removed delegate: " + nm.getNotificationDelegate());
- nm.cancelAll();
- finish();
- }
-}
diff --git a/tests/app/NotificationProvider/AndroidManifest.xml b/tests/app/NotificationProvider/AndroidManifest.xml
index 09ae4b0..4c6c972 100644
--- a/tests/app/NotificationProvider/AndroidManifest.xml
+++ b/tests/app/NotificationProvider/AndroidManifest.xml
@@ -16,6 +16,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.test.notificationprovider">
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application android:label="Notification Provider">
<activity android:name=".RichNotificationActivity" android:exported="true">
<intent-filter>
diff --git a/tests/app/NotificationTrampolineBase/AndroidManifest.xml b/tests/app/NotificationTrampolineBase/AndroidManifest.xml
index 093495f..418872f 100644
--- a/tests/app/NotificationTrampolineBase/AndroidManifest.xml
+++ b/tests/app/NotificationTrampolineBase/AndroidManifest.xml
@@ -16,6 +16,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.test.notificationtrampoline">
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application>
<service
android:name=".NotificationTrampolineTestService"
diff --git a/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java b/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java
index e230eb6..120186b 100644
--- a/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java
+++ b/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java
@@ -116,7 +116,8 @@
startTargetActivity();
}
};
- registerReceiver(mReceiver, new IntentFilter(mReceiverAction));
+ registerReceiver(mReceiver, new IntentFilter(mReceiverAction),
+ Context.RECEIVER_EXPORTED_UNAUDITED);
Intent intent = new Intent(mReceiverAction);
postNotification(notificationId,
PendingIntent.getBroadcast(context, 0, intent, PI_FLAGS));
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index 737dfaa..e8a327b 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -65,6 +65,7 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application android:label="Android TestCase"
android:icon="@drawable/size_48x48"
@@ -366,7 +367,6 @@
<activity android:name="android.app.stubs.ActivityManagerStubCrashActivity"
android:label="ActivityManagerStubCrashActivity"
- android:multiprocess="true"
android:process=":ActivityManagerStubCrashActivity"
android:exported="true">
<intent-filter>
@@ -477,16 +477,14 @@
</intent-filter>
</service>
- <service android:name="android.app.stubs.ToggleableTestTileService"
- android:exported="true"
- android:label="BooleanTestTileService"
- android:icon="@drawable/robot"
- android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+ <service android:name="android.app.stubs.NotExportedTestTileService"
+ android:exported="false"
+ android:label="NotExportedTestTileService"
+ android:icon="@drawable/robot"
+ android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE"/>
</intent-filter>
- <meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE"
- android:value="true"/>
</service>
<activity android:name="android.app.stubs.AutomaticZenRuleActivity"
diff --git a/tests/app/app/src/android/app/stubs/NotExportedTestTileService.java b/tests/app/app/src/android/app/stubs/NotExportedTestTileService.java
new file mode 100644
index 0000000..f0ac974
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/NotExportedTestTileService.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.content.ComponentName;
+import android.service.quicksettings.TileService;
+
+public class NotExportedTestTileService extends TileService {
+
+ public static ComponentName getComponentName() {
+ return new ComponentName(NotExportedTestTileService.class.getPackage().getName(),
+ NotExportedTestTileService.class.getName());
+ }
+}
diff --git a/tests/app/app/src/android/app/stubs/TestTileService.java b/tests/app/app/src/android/app/stubs/TestTileService.java
index 623625b..c10ef8f 100644
--- a/tests/app/app/src/android/app/stubs/TestTileService.java
+++ b/tests/app/app/src/android/app/stubs/TestTileService.java
@@ -19,85 +19,10 @@
import android.content.ComponentName;
import android.service.quicksettings.TileService;
-import java.util.concurrent.atomic.AtomicBoolean;
-
public class TestTileService extends TileService {
- public static final String TAG = "TestTileService";
- public static final String PKG = "android.app.stubs";
- public static final int ICON_ID = R.drawable.robot;
-
- private static TestTileService sTestTileService = null;
- AtomicBoolean isConnected = new AtomicBoolean(false);
- AtomicBoolean isListening = new AtomicBoolean(false);
- AtomicBoolean hasBeenClicked = new AtomicBoolean(false);
-
- public static String getId() {
- return String.format("%s/%s", TestTileService.class.getPackage().getName(),
- TestTileService.class.getName());
- }
-
public static ComponentName getComponentName() {
return new ComponentName(TestTileService.class.getPackage().getName(),
TestTileService.class.getName());
}
-
- @Override
- public void onCreate() {
- super.onCreate();
- }
-
- public static TestTileService getInstance() {
- return TestTileService.sTestTileService;
- }
-
- public void setInstance(TestTileService tile) {
- sTestTileService = tile;
- }
-
- public static boolean isConnected() {
- return getInstance() != null && getInstance().isConnected.get();
- }
-
- public static boolean isListening() {
- return getInstance().isListening.get();
- }
-
- public static boolean hasBeenClicked() {
- return getInstance().hasBeenClicked.get();
- }
-
- @Override
- public void onStartListening() {
- super.onStartListening();
- isListening.set(true);
- }
-
- @Override
- public void onStopListening() {
- super.onStopListening();
- isListening.set(false);
- }
-
- @Override
- public void onClick() {
- super.onClick();
- hasBeenClicked.set(true);
- }
-
- @Override
- public void onTileAdded() {
- super.onTileAdded();
- setInstance(this);
- isConnected.set(true);
- }
-
- @Override
- public void onTileRemoved() {
- super.onTileRemoved();
- setInstance(null);
- isConnected.set(false);
- isListening.set(false);
- hasBeenClicked.set(false);
- }
}
diff --git a/tests/app/app/src/android/app/stubs/ToggleableTestTileService.java b/tests/app/app/src/android/app/stubs/ToggleableTestTileService.java
deleted file mode 100644
index 9e91076..0000000
--- a/tests/app/app/src/android/app/stubs/ToggleableTestTileService.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR 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.content.ComponentName;
-import android.service.quicksettings.Tile;
-
-public class ToggleableTestTileService extends TestTileService {
- public static final String TAG = "ToggleableTestTileService";
- public static final String PKG = "android.app.stubs";
- public static final int ICON_ID = R.drawable.robot;
-
- private static TestTileService sTestTileService = null;
-
- public static boolean isConnected() {
- return getInstance() != null && getInstance().isConnected.get();
- }
-
- public static boolean isListening() {
- return getInstance().isListening.get();
- }
-
- public static TestTileService getInstance() {
- return ToggleableTestTileService.sTestTileService;
- }
-
- @Override
- public void setInstance(TestTileService tile) {
- sTestTileService = tile;
- }
-
- public static String getId() {
- return String.format("%s/%s", ToggleableTestTileService.class.getPackage().getName(),
- ToggleableTestTileService.class.getName());
- }
-
- public static ComponentName getComponentName() {
- return new ComponentName(ToggleableTestTileService.class.getPackage().getName(),
- ToggleableTestTileService.class.getName());
- }
-
- public void toggleState() {
- if (isListening()) {
- Tile tile = getInstance().getQsTile();
- switch(tile.getState()) {
- case Tile.STATE_ACTIVE:
- tile.setState(Tile.STATE_INACTIVE);
- break;
- case Tile.STATE_INACTIVE:
- tile.setState(Tile.STATE_ACTIVE);
- break;
- default:
- break;
- }
- tile.updateTile();
- }
- }
-}
diff --git a/tests/app/shared/AndroidManifest.xml b/tests/app/shared/AndroidManifest.xml
index 247a2a8..2eea680 100644
--- a/tests/app/shared/AndroidManifest.xml
+++ b/tests/app/shared/AndroidManifest.xml
@@ -17,6 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.app.stubs.shared">
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application>
<service
android:name="android.app.stubs.shared.CloseSystemDialogsTestService"
diff --git a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
index 10daf75..a8b6075 100644
--- a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
@@ -61,6 +61,7 @@
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -78,10 +79,10 @@
public class ActivityManagerFgsBgStartTest {
private static final String TAG = ActivityManagerFgsBgStartTest.class.getName();
- private static final String STUB_PACKAGE_NAME = "android.app.stubs";
- private static final String PACKAGE_NAME_APP1 = "com.android.app1";
- private static final String PACKAGE_NAME_APP2 = "com.android.app2";
- private static final String PACKAGE_NAME_APP3 = "com.android.app3";
+ static final String STUB_PACKAGE_NAME = "android.app.stubs";
+ static final String PACKAGE_NAME_APP1 = "com.android.app1";
+ static final String PACKAGE_NAME_APP2 = "com.android.app2";
+ static final String PACKAGE_NAME_APP3 = "com.android.app3";
private static final String KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED =
"default_fgs_starts_restriction_enabled";
@@ -99,7 +100,7 @@
| PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
| PROCESS_CAPABILITY_NETWORK);
- private static final int WAITFOR_MSEC = 10000;
+ static final int WAITFOR_MSEC = 10000;
private static final int TEMP_ALLOWLIST_DURATION_MS = 2000;
@@ -1809,6 +1810,133 @@
}
}
+ @Test
+ public void testFgsStartInBackgroundRestrictions() throws Exception {
+ ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+ PACKAGE_NAME_APP1, 0);
+ ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
+ PACKAGE_NAME_APP2, 0);
+ WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+ WAITFOR_MSEC);
+ WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
+ WAITFOR_MSEC);
+ WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ final String dumpCommand = "dumpsys activity services " + PACKAGE_NAME_APP2
+ + "/android.app.stubs.LocalForegroundService";
+ final long shortWaitMsec = 5_000;
+ try {
+ // Enable the FGS background startForeground() restriction.
+ enableFgsRestriction(true, true, null);
+
+ // Set background restriction for APP2
+ setAppOp(PACKAGE_NAME_APP2, "RUN_ANY_IN_BACKGROUND", false);
+
+ // Start the APP1 into the TOP state.
+ allowBgActivityStart(PACKAGE_NAME_APP1, true);
+ CommandReceiver.sendCommand(mContext,
+ CommandReceiver.COMMAND_START_ACTIVITY,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+ uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_TOP);
+
+ // APP1 binds to APP2.
+ CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, Context.BIND_INCLUDE_CAPABILITIES, null);
+
+ // APP2 gets proc state BOUND_TOP.
+ uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_BOUND_TOP);
+
+ waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
+
+ // START FGS in APP2.
+ CommandReceiver.sendCommand(mContext,
+ CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+ PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
+ waiter.doWait(WAITFOR_MSEC);
+
+ SystemClock.sleep(shortWaitMsec);
+
+ String[] dumpLines = CtsAppTestUtils.executeShellCmd(
+ mInstrumentation, dumpCommand).split("\n");
+ assertNotNull(findLine(dumpLines, "isForeground=true"));
+
+ // Finish the activity in APP1
+ CommandReceiver.sendCommand(mContext,
+ CommandReceiver.COMMAND_STOP_ACTIVITY,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+ mInstrumentation.getUiAutomation().performGlobalAction(
+ AccessibilityService.GLOBAL_ACTION_HOME);
+
+ // APP1 should have been cached state now.
+ uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_CACHED_EMPTY);
+
+ // Th FGS in APP2 should have been normal service state now.
+ uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_SERVICE);
+
+ waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
+
+ // START FGS in APP1
+ CommandReceiver.sendCommand(mContext,
+ CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+ uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_FG_SERVICE);
+ waiter.doWait(WAITFOR_MSEC);
+
+ // APP2 should be in FGS state too now.
+ uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_FG_SERVICE);
+
+ waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
+
+ // START FGS in APP2.
+ CommandReceiver.sendCommand(mContext,
+ CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+ PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
+ waiter.doWait(WAITFOR_MSEC);
+
+ SystemClock.sleep(shortWaitMsec);
+
+ dumpLines = CtsAppTestUtils.executeShellCmd(
+ mInstrumentation, dumpCommand).split("\n");
+ assertNotNull(findLine(dumpLines, "isForeground=true"));
+
+ // Set background restriction for APP1.
+ setAppOp(PACKAGE_NAME_APP1, "RUN_ANY_IN_BACKGROUND", false);
+
+ // Both of them should have normal service state now.
+ uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_SERVICE);
+ uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_SERVICE);
+ } finally {
+ uid1Watcher.finish();
+ uid2Watcher.finish();
+ CtsAppTestUtils.executeShellCmd(mInstrumentation,
+ "appops reset " + PACKAGE_NAME_APP1);
+ CtsAppTestUtils.executeShellCmd(mInstrumentation,
+ "appops reset " + PACKAGE_NAME_APP2);
+ }
+ }
+
+ /**
+ * Find a line containing {@code label} in {@code lines}.
+ */
+ private String findLine(String[] lines, CharSequence label) {
+ for (String line: lines) {
+ if (line.contains(label)) {
+ return line;
+ }
+ }
+ return null;
+ }
+
/**
* When PowerExemptionManager.addToTemporaryAllowList() is called more than one time, the second
* call can extend the duration of the first call if the first call has not expired yet.
@@ -1964,6 +2092,8 @@
try {
defaultBehavior = Integer.parseInt(defaultBehaviorStr);
} catch (NumberFormatException e) {
+ Log.e("ActivityManagerFgsBgStartTest",
+ "getPushMessagingOverQuotaBehavior:", e);
}
}
return defaultBehavior;
diff --git a/tests/app/src/android/app/cts/ActivityManagerTest.java b/tests/app/src/android/app/cts/ActivityManagerTest.java
index 770d305..d5de40a 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -500,6 +500,138 @@
public void testGetProcessInErrorState() throws Exception {
List<ActivityManager.ProcessErrorStateInfo> errList = null;
errList = mActivityManager.getProcessesInErrorState();
+ assertNull(errList);
+
+ // Setup the ANR monitor.
+ final AmMonitor monitor = new AmMonitor(mInstrumentation, null);
+ final ApplicationInfo app1Info = mTargetContext.getPackageManager().getApplicationInfo(
+ PACKAGE_NAME_APP1, 0);
+ final ApplicationInfo stubInfo = mTargetContext.getPackageManager().getApplicationInfo(
+ STUB_PACKAGE_NAME, 0);
+ final WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+ WAITFOR_MSEC);
+ final String crashActivityName = "ActivityManagerStubCrashActivity";
+
+ final SettingsSession<Integer> showOnFirstCrash = new SettingsSession<>(
+ Settings.Global.getUriFor(Settings.Global.SHOW_FIRST_CRASH_DIALOG),
+ Settings.Global::getInt, Settings.Global::putInt);
+ final SettingsSession<Integer> showBackground = new SettingsSession<>(
+ Settings.Secure.getUriFor(Settings.Secure.ANR_SHOW_BACKGROUND),
+ Settings.Secure::getInt, Settings.Secure::putInt);
+ try {
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ showOnFirstCrash.set(1);
+ showBackground.set(1);
+ });
+
+ CommandReceiver.sendCommand(mTargetContext,
+ CommandReceiver.COMMAND_START_ACTIVITY,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+ uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_TOP,
+ new Integer(ActivityManager.PROCESS_CAPABILITY_ALL));
+
+ // Sleep a while to let things go through.
+ Thread.sleep(WAIT_TIME);
+
+ // Now tell it goto ANR.
+ CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_SELF_INDUCED_ANR,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+ // Verify we got the ANR.
+ assertTrue(monitor.waitFor(AmMonitor.WAIT_FOR_EARLY_ANR, WAITFOR_MSEC));
+
+ // Let it continue.
+ monitor.sendCommand(AmMonitor.CMD_CONTINUE);
+
+ // Now it should've reached the normal ANR process.
+ assertTrue(monitor.waitFor(AmMonitor.WAIT_FOR_ANR, WAITFOR_MSEC * 3));
+
+ // Continue again, we need to see the ANR dialog in order to get the error report.
+ monitor.sendCommand(AmMonitor.CMD_CONTINUE);
+
+ // Sleep a while to let things go through.
+ Thread.sleep(WAIT_TIME);
+
+ // We shouldn't be able to read the error state info of that.
+ errList = mActivityManager.getProcessesInErrorState();
+ assertNull(errList);
+
+ // Shell should have the access.
+ final List<ActivityManager.ProcessErrorStateInfo>[] holder = new List[1];
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ holder[0] = mActivityManager.getProcessesInErrorState();
+ });
+ assertNotNull(holder[0]);
+ assertEquals(1, holder[0].size());
+ verifyProcessErrorStateInfo(holder[0].get(0),
+ ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,
+ app1Info.uid,
+ PACKAGE_NAME_APP1);
+
+ // Start a crashing activity in remote process with the same UID.
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(STUB_PACKAGE_NAME, STUB_PACKAGE_NAME + "." + crashActivityName);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mTargetContext.startActivity(intent);
+
+ // Wait for the crash.
+ assertTrue(monitor.waitFor(AmMonitor.WAIT_FOR_CRASHED, WAITFOR_MSEC));
+
+ // Let it continue, we need to see the crash dialog in order to get the error report.
+ monitor.sendCommand(AmMonitor.CMD_CONTINUE);
+
+ // Sleep a while to let things go through.
+ Thread.sleep(WAIT_TIME);
+
+ // We should be able to see this crash info.
+ errList = mActivityManager.getProcessesInErrorState();
+ assertNotNull(errList);
+ assertEquals(1, errList.size());
+
+ verifyProcessErrorStateInfo(errList.get(0),
+ ActivityManager.ProcessErrorStateInfo.CRASHED,
+ stubInfo.uid,
+ STUB_PACKAGE_NAME + ":" + crashActivityName);
+
+ // Shell should have the access to all of the crash info here.
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ holder[0] = mActivityManager.getProcessesInErrorState();
+ });
+ assertNotNull(holder[0]);
+ assertEquals(2, holder[0].size());
+ // The return result is not sorted.
+ final ActivityManager.ProcessErrorStateInfo t0 = holder[0].get(0);
+ final ActivityManager.ProcessErrorStateInfo t1 = holder[0].get(1);
+ final ActivityManager.ProcessErrorStateInfo info0 = t0.uid == stubInfo.uid ? t0 : t1;
+ final ActivityManager.ProcessErrorStateInfo info1 = t1.uid == app1Info.uid ? t1 : t0;
+
+ verifyProcessErrorStateInfo(info0,
+ ActivityManager.ProcessErrorStateInfo.CRASHED,
+ stubInfo.uid,
+ STUB_PACKAGE_NAME + ":" + crashActivityName);
+ verifyProcessErrorStateInfo(info1,
+ ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,
+ app1Info.uid,
+ PACKAGE_NAME_APP1);
+ } finally {
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ showOnFirstCrash.close();
+ showBackground.close();
+ });
+ monitor.finish();
+ uid1Watcher.finish();
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ mActivityManager.forceStopPackage(PACKAGE_NAME_APP1);
+ });
+ }
+ }
+
+ private void verifyProcessErrorStateInfo(ActivityManager.ProcessErrorStateInfo info,
+ int condition, int uid, String processName) throws Exception {
+ assertEquals(condition, info.condition);
+ assertEquals(uid, info.uid);
+ assertEquals(processName, info.processName);
}
public void testGetDeviceConfigurationInfo() {
diff --git a/tests/app/src/android/app/cts/ActivityOptionsTest.java b/tests/app/src/android/app/cts/ActivityOptionsTest.java
index eab44c5..ad58410 100644
--- a/tests/app/src/android/app/cts/ActivityOptionsTest.java
+++ b/tests/app/src/android/app/cts/ActivityOptionsTest.java
@@ -16,7 +16,10 @@
package android.app.cts;
+import static com.google.common.truth.Truth.assertThat;
+
import android.app.ActivityOptions;
+import android.graphics.Rect;
import android.os.Bundle;
import android.test.AndroidTestCase;
@@ -28,4 +31,29 @@
assertNotNull(bundle);
}
+
+ public void testActivityOptionsBundle_fromBundle() {
+ final int displayId = 9;
+ final Rect bounds = new Rect(0, 10, 100, 90);
+ // Construct some options with set values
+ ActivityOptions opts = ActivityOptions.makeBasic();
+ opts.setLaunchDisplayId(displayId);
+ opts.setLockTaskEnabled(true);
+ opts.setLaunchBounds(bounds);
+
+ Bundle optsBundle = opts.toBundle();
+
+ ActivityOptions opts2 = ActivityOptions.fromBundle(optsBundle);
+ assertThat(opts2.getLaunchDisplayId()).isEqualTo(displayId);
+ assertThat(opts2.getLockTaskMode()).isTrue();
+ assertThat(opts2.getLaunchBounds()).isEqualTo(bounds);
+ }
+
+ public void testGetSetPendingIntentBackgroundActivityLaunchAllowed() {
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(true);
+ assertThat(options.isPendingIntentBackgroundActivityLaunchAllowed()).isTrue();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ assertThat(options.isPendingIntentBackgroundActivityLaunchAllowed()).isFalse();
+ }
}
diff --git a/tests/app/src/android/app/cts/AlertDialogTest.java b/tests/app/src/android/app/cts/AlertDialogTest.java
index 4710ac2..28d1579 100644
--- a/tests/app/src/android/app/cts/AlertDialogTest.java
+++ b/tests/app/src/android/app/cts/AlertDialogTest.java
@@ -35,6 +35,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.After;
import org.junit.Before;
@@ -75,7 +76,7 @@
});
PollingCheck.waitFor(mActivity.getDialog()::isShowing);
- PollingCheck.waitFor(mActivity.getDialog().getWindow().getDecorView()::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity.getDialog().getWindow());
}
@Test
diff --git a/tests/app/src/android/app/cts/AlertDialog_BuilderCursorTest.java b/tests/app/src/android/app/cts/AlertDialog_BuilderCursorTest.java
index 75017c1..6dc1067 100644
--- a/tests/app/src/android/app/cts/AlertDialog_BuilderCursorTest.java
+++ b/tests/app/src/android/app/cts/AlertDialog_BuilderCursorTest.java
@@ -36,7 +36,7 @@
import androidx.test.InstrumentationRegistry;
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
import java.io.File;
@@ -103,7 +103,7 @@
mInstrumentation = getInstrumentation();
mContext = getActivity();
- PollingCheck.waitFor(() -> getActivity().hasWindowFocus());
+ WindowUtil.waitForFocus(getActivity());
mListView = null;
mDialog = null;
diff --git a/tests/app/src/android/app/cts/AlertDialog_BuilderTest.java b/tests/app/src/android/app/cts/AlertDialog_BuilderTest.java
index d27d4e8..449529e 100644
--- a/tests/app/src/android/app/cts/AlertDialog_BuilderTest.java
+++ b/tests/app/src/android/app/cts/AlertDialog_BuilderTest.java
@@ -53,6 +53,7 @@
import androidx.test.rule.ActivityTestRule;
import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.Before;
import org.junit.Rule;
@@ -99,7 +100,7 @@
mInstrumentation = InstrumentationRegistry.getInstrumentation();
Activity activity = mActivityRule.getActivity();
mContext = activity;
- PollingCheck.waitFor(activity::hasWindowFocus);
+ WindowUtil.waitForFocus(activity);
}
@Test
diff --git a/tests/app/src/android/app/cts/BaseTileServiceTest.java b/tests/app/src/android/app/cts/BaseTileServiceTest.java
deleted file mode 100644
index c3eedb4..0000000
--- a/tests/app/src/android/app/cts/BaseTileServiceTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR 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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assume.assumeTrue;
-
-import android.app.stubs.TestTileService;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.service.quicksettings.TileService;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.Until;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-
-@RunWith(AndroidJUnit4.class)
-public abstract class BaseTileServiceTest {
-
- protected abstract String getTag();
- protected abstract String getComponentName();
- protected abstract TileService getTileServiceInstance();
- protected abstract void waitForConnected(boolean state) throws InterruptedException;
- protected abstract void waitForListening(boolean state) throws InterruptedException;
- protected Context mContext;
-
- final static String DUMP_COMMAND =
- "dumpsys activity service com.android.systemui/.SystemUIService QSTileHost";
-
- // Time between checks for state we expect.
- protected static final long CHECK_DELAY = 250;
- // Number of times to check before failing. This is set so the maximum wait time is about 4s,
- // as some tests were observed to take around 3s.
- protected static final long CHECK_RETRIES = 15;
- // Timeout to wait for launcher
- protected static final long TIMEOUT = 8000;
-
- protected TileService mTileService;
- private Intent homeIntent;
- private String mLauncherPackage;
-
- @Before
- public void setUp() throws Exception {
- assumeTrue(TileService.isQuickSettingsSupported());
- mContext = InstrumentationRegistry.getContext();
- homeIntent = new Intent(Intent.ACTION_MAIN);
- homeIntent.addCategory(Intent.CATEGORY_HOME);
-
- mLauncherPackage = mContext.getPackageManager().resolveActivity(homeIntent,
- PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
-
- // Wait for home
- UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- device.pressHome();
- device.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
- }
-
- @After
- public void tearDown() throws Exception {
- expandSettings(false);
- toggleServiceAccess(getComponentName(), false);
- waitForConnected(false);
- assertNull(TestTileService.getInstance());
- }
-
- protected void startTileService() throws Exception {
- toggleServiceAccess(getComponentName(), true);
- waitForConnected(true); // wait for service to be bound
- mTileService = getTileServiceInstance();
- assertNotNull(mTileService);
- }
-
- protected void toggleServiceAccess(String componentName, boolean on) throws Exception {
- String command = " cmd statusbar " + (on ? "add-tile " : "remove-tile ")
- + componentName;
-
- executeShellCommand(command);
- }
-
- public String executeShellCommand(String command) throws IOException {
- Log.i(getTag(), "Shell command: " + command);
- try {
- return SystemUtil.runShellCommand(getInstrumentation(), command);
- } catch (IOException e) {
- //bubble it up
- Log.e(getTag(), "Error running shell command: " + command);
- throw new IOException(e);
- }
- }
-
- protected void expandSettings(boolean expand) throws Exception {
- executeShellCommand(" cmd statusbar " + (expand ? "expand-settings" : "collapse"));
- Thread.sleep(600); // wait for animation
- }
-
- protected void initializeAndListen() throws Exception {
- startTileService();
- expandSettings(true);
- waitForListening(true);
- }
-
- /**
- * Find a line containing {@code label} in {@code lines}.
- */
- protected String findLine(String[] lines, CharSequence label) {
- for (String line: lines) {
- if (line.contains(label)) {
- return line;
- }
- }
- return null;
- }
-}
diff --git a/tests/app/src/android/app/cts/BooleanTileServiceTest.java b/tests/app/src/android/app/cts/BooleanTileServiceTest.java
deleted file mode 100644
index 6ffa763..0000000
--- a/tests/app/src/android/app/cts/BooleanTileServiceTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR 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 org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.app.stubs.ToggleableTestTileService;
-import android.service.quicksettings.Tile;
-import android.service.quicksettings.TileService;
-
-import org.junit.Test;
-
-public class BooleanTileServiceTest extends BaseTileServiceTest {
- private final static String TAG = "BooleanTileServiceTest";
-
- @Test
- public void testTileIsBoundAndListening() throws Exception {
- startTileService();
- expandSettings(true);
- waitForListening(true);
- }
-
- @Test
- public void testTileInDumpAndHasBooleanState() throws Exception {
- initializeAndListen();
-
- final CharSequence tileLabel = mTileService.getQsTile().getLabel();
-
- final String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
- final String line = findLine(dumpLines, tileLabel);
- assertNotNull(line);
- assertTrue(line.trim().startsWith("BooleanState"));
- }
-
- @Test
- public void testTileStartsInactive() throws Exception {
- initializeAndListen();
-
- assertEquals(Tile.STATE_INACTIVE, mTileService.getQsTile().getState());
- }
-
- @Test
- public void testValueTracksState() throws Exception {
- initializeAndListen();
-
- final CharSequence tileLabel = mTileService.getQsTile().getLabel();
-
- String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
- String line = findLine(dumpLines, tileLabel);
-
- // Tile starts inactive
- assertTrue(line.contains("value=false"));
-
- ((ToggleableTestTileService) mTileService).toggleState();
-
- // Close and open QS to make sure that state is refreshed
- expandSettings(false);
- waitForListening(false);
- expandSettings(true);
- waitForListening(true);
-
- assertEquals(Tile.STATE_ACTIVE, mTileService.getQsTile().getState());
-
- dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
- line = findLine(dumpLines, tileLabel);
-
- assertTrue(line.contains("value=true"));
- }
-
- @Override
- protected String getTag() {
- return TAG;
- }
-
- @Override
- protected String getComponentName() {
- return ToggleableTestTileService.getComponentName().flattenToString();
- }
-
- @Override
- protected TileService getTileServiceInstance() {
- return ToggleableTestTileService.getInstance();
- }
-
- /**
- * Waits for the TileService to be in the expected listening state. If it times out, it fails
- * the test
- * @param state desired listening state
- * @throws InterruptedException
- */
- @Override
- protected void waitForListening(boolean state) throws InterruptedException {
- int ct = 0;
- while (ToggleableTestTileService.isListening() != state && (ct++ < CHECK_RETRIES)) {
- Thread.sleep(CHECK_DELAY);
- }
- assertEquals(state, ToggleableTestTileService.isListening());
- }
-
- /**
- * Waits for the TileService to be in the expected connected state. If it times out, it fails
- * the test
- * @param state desired connected state
- * @throws InterruptedException
- */
- @Override
- protected void waitForConnected(boolean state) throws InterruptedException {
- int ct = 0;
- while (ToggleableTestTileService.isConnected() != state && (ct++ < CHECK_RETRIES)) {
- Thread.sleep(CHECK_DELAY);
- }
- assertEquals(state, ToggleableTestTileService.isConnected());
- }
-}
diff --git a/tests/app/src/android/app/cts/BroadcastOptionsTest.java b/tests/app/src/android/app/cts/BroadcastOptionsTest.java
index c3b4e89..a63f91b 100644
--- a/tests/app/src/android/app/cts/BroadcastOptionsTest.java
+++ b/tests/app/src/android/app/cts/BroadcastOptionsTest.java
@@ -16,16 +16,30 @@
package android.app.cts;
+import static android.app.cts.ActivityManagerFgsBgStartTest.PACKAGE_NAME_APP1;
+import static android.app.cts.ActivityManagerFgsBgStartTest.PACKAGE_NAME_APP2;
+import static android.app.cts.ActivityManagerFgsBgStartTest.WAITFOR_MSEC;
+import static android.app.stubs.LocalForegroundService.ACTION_START_FGS_RESULT;
+
import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertThrows;
import android.app.BroadcastOptions;
+import android.app.Instrumentation;
+import android.app.cts.android.app.cts.tools.WaitForBroadcast;
+import android.app.stubs.CommandReceiver;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerExemptionManager;
+import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.compatibility.common.util.SystemUtil;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -74,9 +88,12 @@
BroadcastOptions bo;
bo = BroadcastOptions.makeBasic();
+ Bundle bundle = bo.toBundle();
- // If no options are set, toBundle() should return null
- assertNull(bo.toBundle());
+ // Only background activity launch key is set.
+ assertEquals(1, bundle.size());
+ // TODO: Use BroadcastOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED instead.
+ assertTrue(bundle.containsKey("android.pendingIntent.backgroundActivityAllowed"));
// Check the default values about temp-allowlist.
assertBroadcastOption_noTemporaryAppAllowList(bo);
@@ -162,4 +179,88 @@
final BroadcastOptions cloned = cloneViaBundle(bo);
assertEquals(Build.VERSION_CODES.P, bo.getMaxManifestReceiverApiLevel());
}
+
+ @Test
+ public void testGetSetPendingIntentBackgroundActivityLaunchAllowed() {
+ BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(true);
+ assertTrue(options.isPendingIntentBackgroundActivityLaunchAllowed());
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ assertFalse(options.isPendingIntentBackgroundActivityLaunchAllowed());
+ }
+
+ private void assertBroadcastSuccess(BroadcastOptions options) {
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ final WaitForBroadcast waiter = new WaitForBroadcast(instrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
+ CommandReceiver.sendCommandWithBroadcastOptions(instrumentation.getContext(),
+ CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null,
+ options.toBundle());
+ waiter.doWait(WAITFOR_MSEC);
+ }
+
+ private void assertBroadcastFailure(BroadcastOptions options) {
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ final WaitForBroadcast waiter = new WaitForBroadcast(instrumentation.getTargetContext());
+ waiter.prepare(ACTION_START_FGS_RESULT);
+ CommandReceiver.sendCommandWithBroadcastOptions(instrumentation.getContext(),
+ CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null,
+ options.toBundle());
+ assertThrows(Exception.class, () -> waiter.doWait(WAITFOR_MSEC));
+ }
+
+ @Test
+ public void testRequireCompatChange_simple() {
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ final int uid = android.os.Process.myUid();
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+
+ // Default passes
+ assertTrue(options.testRequireCompatChange(uid));
+ assertTrue(cloneViaBundle(options).testRequireCompatChange(uid));
+
+ // Verify both enabled and disabled
+ options.setRequireCompatChange(BroadcastOptions.CHANGE_ALWAYS_ENABLED, true);
+ assertTrue(options.testRequireCompatChange(uid));
+ assertTrue(cloneViaBundle(options).testRequireCompatChange(uid));
+ options.setRequireCompatChange(BroadcastOptions.CHANGE_ALWAYS_ENABLED, false);
+ assertFalse(options.testRequireCompatChange(uid));
+ assertFalse(cloneViaBundle(options).testRequireCompatChange(uid));
+
+ // And back to default passes
+ options.clearRequireCompatChange();
+ assertTrue(options.testRequireCompatChange(uid));
+ assertTrue(cloneViaBundle(options).testRequireCompatChange(uid));
+ });
+ }
+
+ @Test
+ public void testRequireCompatChange_enabled_success() {
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setRequireCompatChange(BroadcastOptions.CHANGE_ALWAYS_ENABLED, true);
+ assertBroadcastSuccess(options);
+ }
+
+ @Test
+ public void testRequireCompatChange_enabled_failure() {
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setRequireCompatChange(BroadcastOptions.CHANGE_ALWAYS_DISABLED, true);
+ assertBroadcastFailure(options);
+ }
+
+ @Test
+ public void testRequireCompatChange_disabled_success() {
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setRequireCompatChange(BroadcastOptions.CHANGE_ALWAYS_DISABLED, false);
+ assertBroadcastSuccess(options);
+ }
+
+ @Test
+ public void testRequireCompatChange_disabled_failure() {
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setRequireCompatChange(BroadcastOptions.CHANGE_ALWAYS_ENABLED, false);
+ assertBroadcastFailure(options);
+ }
}
diff --git a/tests/app/src/android/app/cts/CloseSystemDialogsTest.java b/tests/app/src/android/app/cts/CloseSystemDialogsTest.java
index f54c5af..da2c43a 100644
--- a/tests/app/src/android/app/cts/CloseSystemDialogsTest.java
+++ b/tests/app/src/android/app/cts/CloseSystemDialogsTest.java
@@ -28,6 +28,7 @@
import static org.junit.Assume.assumeTrue;
import static org.testng.Assert.assertThrows;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.app.UiAutomation;
@@ -47,7 +48,10 @@
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Looper;
+import android.os.Process;
import android.os.ResultReceiver;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
import android.provider.Settings;
import android.server.wm.WindowManagerStateHelper;
import android.view.Display;
@@ -118,6 +122,7 @@
public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mContext = mInstrumentation.getTargetContext();
+ PermissionUtils.grantPermission(APP_SELF, Manifest.permission.POST_NOTIFICATIONS);
mResolver = mContext.getContentResolver();
mMainHandler = new Handler(Looper.getMainLooper());
toggleListenerAccess(mContext, true);
@@ -163,6 +168,15 @@
compat(APP_COMPAT_RESET, ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, APP_HELPER);
compat(APP_COMPAT_RESET, "NOTIFICATION_TRAMPOLINE_BLOCK", APP_HELPER);
mNotificationListener.resetData();
+ // Use test API to prevent PermissionManager from killing the test process when revoking
+ // permission.
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mContext.getSystemService(PermissionManager.class)
+ .revokePostNotificationPermissionWithoutKillForTest(
+ mContext.getPackageName(),
+ Process.myUserHandle().getIdentifier()),
+ Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
}
/** Intent.ACTION_CLOSE_SYSTEM_DIALOGS */
diff --git a/tests/app/src/android/app/cts/DialogTest.java b/tests/app/src/android/app/cts/DialogTest.java
index bbc57be..c22798c 100755
--- a/tests/app/src/android/app/cts/DialogTest.java
+++ b/tests/app/src/android/app/cts/DialogTest.java
@@ -63,6 +63,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.After;
import org.junit.Before;
@@ -112,7 +113,7 @@
mScenario.onActivity(activity -> {
mActivity = activity;
});
- PollingCheck.waitFor(mActivity.getDialog().getWindow().getDecorView()::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity.getDialog().getWindow());
}
@UiThreadTest
diff --git a/tests/app/src/android/app/cts/InstrumentationTest.java b/tests/app/src/android/app/cts/InstrumentationTest.java
index 0d7b395..70499b8 100644
--- a/tests/app/src/android/app/cts/InstrumentationTest.java
+++ b/tests/app/src/android/app/cts/InstrumentationTest.java
@@ -48,8 +48,8 @@
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
-import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.WindowUtil;
import java.util.List;
@@ -75,7 +75,7 @@
mIntent = new Intent(mContext, InstrumentationTestActivity.class);
mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mActivity = (InstrumentationTestActivity) mInstrumentation.startActivitySync(mIntent);
- PollingCheck.waitFor(mActivity::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity);
}
protected void tearDown() throws Exception {
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index e3c5c9a..0fb6f50 100755
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -16,6 +16,10 @@
package android.app.cts;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
+import static android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS;
+import static android.app.Notification.CATEGORY_CALL;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
@@ -39,6 +43,7 @@
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
@@ -83,6 +88,7 @@
import android.app.stubs.AutomaticZenRuleActivity;
import android.app.stubs.BubbledActivity;
import android.app.stubs.BubblesTestService;
+import android.app.stubs.GetResultActivity;
import android.app.stubs.R;
import android.app.stubs.SendBubbleActivity;
import android.app.stubs.TestNotificationListener;
@@ -119,6 +125,8 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
import android.platform.test.annotations.AsbSecurityTest;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Email;
@@ -183,9 +191,12 @@
final boolean DEBUG = false;
static final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest";
- private static final String DELEGATOR = "com.android.test.notificationdelegator";
- private static final String DELEGATE_POST_CLASS = DELEGATOR + ".NotificationDelegateAndPost";
- private static final String REVOKE_CLASS = DELEGATOR + ".NotificationRevoker";
+ private static final String TEST_APP = "com.android.test.notificationapp";
+ private static final String DELEGATE_POST_CLASS = TEST_APP + ".NotificationDelegateAndPost";
+ private static final String REVOKE_CLASS = TEST_APP + ".NotificationRevoker";
+ private static final String MATCHES_CALL_FILTER_CLASS =
+ TEST_APP + ".MatchesCallFilterTestActivity";
+ private static final String MINIMAL_LISTENER_CLASS = TEST_APP + ".TestNotificationListener";
private static final String SHARE_SHORTCUT_ID = "shareShortcut";
private static final String SHARE_SHORTCUT_CATEGORY =
"android.app.stubs.SHARE_SHORTCUT_CATEGORY";
@@ -204,12 +215,28 @@
new ComponentName(TRAMPOLINE_APP_API_30,
"com.android.test.notificationtrampoline.NotificationTrampolineTestService");
+ private static final String STUB_PACKAGE_NAME = "android.app.stubs";
+
private static final long TIMEOUT_LONG_MS = 10000;
private static final long TIMEOUT_MS = 4000;
private static final int MESSAGE_BROADCAST_NOTIFICATION = 1;
private static final int MESSAGE_SERVICE_NOTIFICATION = 2;
private static final int MESSAGE_CLICK_NOTIFICATION = 3;
+ // Constants for creating contacts
+ private static final String ALICE = "Alice";
+ private static final String ALICE_PHONE = "+16175551212";
+ private static final String ALICE_EMAIL = "alice@_foo._bar";
+ private static final String BOB = "Bob";
+ private static final String BOB_PHONE = "+16175553434";
+ private static final String BOB_EMAIL = "bob@_foo._bar";
+
+ // Constants for GetResultActivity and return codes from MatchesCallFilterTestActivity
+ // the permitted/not permitted values need to stay the same as in the test activity.
+ private static final int REQUEST_CODE = 42;
+ private static final int MATCHES_CALL_FILTER_NOT_PERMITTED = 0;
+ private static final int MATCHES_CALL_FILTER_PERMITTED = 1;
+
private PackageManager mPackageManager;
private AudioManager mAudioManager;
private RoleManager mRoleManager;
@@ -231,6 +258,11 @@
@Override
protected void setUp() throws Exception {
super.setUp();
+ PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS);
+ PermissionUtils.grantPermission(STUB_PACKAGE_NAME, POST_NOTIFICATIONS);
+ PermissionUtils.grantPermission(TEST_APP, POST_NOTIFICATIONS);
+ PermissionUtils.grantPermission(TRAMPOLINE_APP, POST_NOTIFICATIONS);
+ PermissionUtils.grantPermission(NOTIFICATIONPROVIDER, POST_NOTIFICATIONS);
// This will leave a set of channels on the device with each test run.
mId = UUID.randomUUID().toString();
mNotificationManager = (NotificationManager) mContext.getSystemService(
@@ -322,6 +354,20 @@
if (mPreviousDefaultBrowser != null) {
restoreDefaultBrowser();
}
+
+ // Use test API to prevent PermissionManager from killing the test process when revoking
+ // permission.
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mContext.getSystemService(PermissionManager.class)
+ .revokePostNotificationPermissionWithoutKillForTest(
+ mContext.getPackageName(),
+ android.os.Process.myUserHandle().getIdentifier()),
+ REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
+ REVOKE_RUNTIME_PERMISSIONS);
+ PermissionUtils.revokePermission(STUB_PACKAGE_NAME, POST_NOTIFICATIONS);
+ PermissionUtils.revokePermission(TEST_APP, POST_NOTIFICATIONS);
+ PermissionUtils.revokePermission(TRAMPOLINE_APP, POST_NOTIFICATIONS);
+ PermissionUtils.revokePermission(NOTIFICATIONPROVIDER, POST_NOTIFICATIONS);
}
private void assertNotificationCancelled(int id, boolean all) {
@@ -504,6 +550,11 @@
}
private void sendNotification(final int id, String groupKey, final int icon) throws Exception {
+ sendNotification(id, groupKey, icon, false, null);
+ }
+
+ private void sendNotification(final int id, String groupKey, final int icon,
+ boolean isCall, Uri phoneNumber) throws Exception {
final Intent intent = new Intent(Intent.ACTION_MAIN, Threads.CONTENT_URI);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP
@@ -512,15 +563,26 @@
final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
PendingIntent.FLAG_MUTABLE);
- final Notification notification =
- new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+ Notification.Builder nb = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(icon)
.setWhen(System.currentTimeMillis())
.setContentTitle("notify#" + id)
.setContentText("This is #" + id + "notification ")
.setContentIntent(pendingIntent)
- .setGroup(groupKey)
- .build();
+ .setGroup(groupKey);
+
+ if (isCall) {
+ nb.setCategory(CATEGORY_CALL);
+ if (phoneNumber != null) {
+ Bundle extras = new Bundle();
+ ArrayList<Person> pList = new ArrayList<>();
+ pList.add(new Person.Builder().setUri(phoneNumber.toString()).build());
+ extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, pList);
+ nb.setExtras(extras);
+ }
+ }
+
+ final Notification notification = nb.build();
mNotificationManager.notify(id, notification);
if (!checkNotificationExistence(id, /*shouldExist=*/ true)) {
@@ -738,6 +800,24 @@
runCommand(command, InstrumentationRegistry.getInstrumentation());
}
+ private boolean hasReadContactsPermission(String pkgName) {
+ return mPackageManager.checkPermission(
+ Manifest.permission.READ_CONTACTS, pkgName)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private void toggleReadContactsPermission(String pkgName, boolean on) {
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ if (on) {
+ mInstrumentation.getUiAutomation().grantRuntimePermission(pkgName,
+ "android.permission.READ_CONTACTS");
+ } else {
+ mInstrumentation.getUiAutomation().revokeRuntimePermission(pkgName,
+ "android.permission.READ_CONTACTS");
+ }
+ });
+ }
+
private void setBubblesGlobal(boolean enabled)
throws InterruptedException {
SystemUtil.runWithShellPermissionIdentity(() ->
@@ -940,6 +1020,17 @@
mContext.unregisterReceiver(mBubbleBroadcastReceiver);
}
+ // Creates a GetResultActivity into which one can call startActivityForResult with
+ // in order to test the outcome of an activity that returns a result code.
+ private GetResultActivity setUpGetResultActivity() {
+ final Intent intent = new Intent(mContext, GetResultActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ GetResultActivity activity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
+ mInstrumentation.waitForIdleSync();
+ activity.clearResult();
+ return activity;
+ }
+
private void sendTrampolineMessage(ComponentName component, int message,
int notificationId, Handler callback) throws Exception {
if (mTrampolineConnection == null) {
@@ -2537,7 +2628,7 @@
public void testNotificationDelegate_grantAndPost() throws Exception {
// grant this test permission to post
final Intent activityIntent = new Intent();
- activityIntent.setPackage(DELEGATOR);
+ activityIntent.setPackage(TEST_APP);
activityIntent.setAction(Intent.ACTION_MAIN);
activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -2550,11 +2641,11 @@
Notification n = new Notification.Builder(mContext, "channel")
.setSmallIcon(android.R.id.icon)
.build();
- mNotificationManager.notifyAsPackage(DELEGATOR, "tag", 0, n);
+ mNotificationManager.notifyAsPackage(TEST_APP, "tag", 0, n);
assertNotNull(findPostedNotification(0, false));
final Intent revokeIntent = new Intent();
- revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+ revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(revokeIntent);
Thread.sleep(1000);
@@ -2563,7 +2654,7 @@
public void testNotificationDelegate_grantAndPostAndCancel() throws Exception {
// grant this test permission to post
final Intent activityIntent = new Intent();
- activityIntent.setPackage(DELEGATOR);
+ activityIntent.setPackage(TEST_APP);
activityIntent.setAction(Intent.ACTION_MAIN);
activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -2576,12 +2667,12 @@
Notification n = new Notification.Builder(mContext, "channel")
.setSmallIcon(android.R.id.icon)
.build();
- mNotificationManager.notifyAsPackage(DELEGATOR, "toBeCanceled", 10000, n);
+ mNotificationManager.notifyAsPackage(TEST_APP, "toBeCanceled", 10000, n);
assertNotNull(findPostedNotification(10000, false));
- mNotificationManager.cancelAsPackage(DELEGATOR, "toBeCanceled", 10000);
+ mNotificationManager.cancelAsPackage(TEST_APP, "toBeCanceled", 10000);
assertNotificationCancelled(10000, false);
final Intent revokeIntent = new Intent();
- revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+ revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(revokeIntent);
Thread.sleep(1000);
@@ -2597,7 +2688,7 @@
// grant this test permission to post
final Intent activityIntent = new Intent();
- activityIntent.setClassName(DELEGATOR, DELEGATE_POST_CLASS);
+ activityIntent.setClassName(TEST_APP, DELEGATE_POST_CLASS);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(activityIntent);
@@ -2607,7 +2698,7 @@
assertNotNull(findPostedNotification(9, true));
try {
- mNotificationManager.cancelAsPackage(DELEGATOR, null, 9);
+ mNotificationManager.cancelAsPackage(TEST_APP, null, 9);
fail("Delegate should not be able to cancel notification they did not post");
} catch (SecurityException e) {
// yay
@@ -2617,7 +2708,7 @@
assertNotNull(findPostedNotification(9, true));
final Intent revokeIntent = new Intent();
- revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+ revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(revokeIntent);
Thread.sleep(1000);
@@ -2626,7 +2717,7 @@
public void testNotificationDelegate_grantAndReadChannels() throws Exception {
// grant this test permission to post
final Intent activityIntent = new Intent();
- activityIntent.setPackage(DELEGATOR);
+ activityIntent.setPackage(TEST_APP);
activityIntent.setAction(Intent.ACTION_MAIN);
activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -2636,14 +2727,14 @@
Thread.sleep(500);
List<NotificationChannel> channels =
- mContext.createPackageContextAsUser(DELEGATOR, /* flags= */ 0, mContext.getUser())
+ mContext.createPackageContextAsUser(TEST_APP, /* flags= */ 0, mContext.getUser())
.getSystemService(NotificationManager.class)
.getNotificationChannels();
assertNotNull(channels);
final Intent revokeIntent = new Intent();
- revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+ revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(revokeIntent);
Thread.sleep(500);
@@ -2652,7 +2743,7 @@
public void testNotificationDelegate_grantAndReadChannel() throws Exception {
// grant this test permission to post
final Intent activityIntent = new Intent();
- activityIntent.setPackage(DELEGATOR);
+ activityIntent.setPackage(TEST_APP);
activityIntent.setAction(Intent.ACTION_MAIN);
activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -2662,14 +2753,14 @@
Thread.sleep(2000);
NotificationChannel channel =
- mContext.createPackageContextAsUser(DELEGATOR, /* flags= */ 0, mContext.getUser())
+ mContext.createPackageContextAsUser(TEST_APP, /* flags= */ 0, mContext.getUser())
.getSystemService(NotificationManager.class)
.getNotificationChannel("channel");
assertNotNull(channel);
final Intent revokeIntent = new Intent();
- revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+ revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(revokeIntent);
Thread.sleep(500);
@@ -2678,7 +2769,7 @@
public void testNotificationDelegate_grantAndRevoke() throws Exception {
// grant this test permission to post
final Intent activityIntent = new Intent();
- activityIntent.setPackage(DELEGATOR);
+ activityIntent.setPackage(TEST_APP);
activityIntent.setAction(Intent.ACTION_MAIN);
activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -2686,10 +2777,10 @@
mContext.startActivity(activityIntent);
Thread.sleep(500);
- assertTrue(mNotificationManager.canNotifyAsPackage(DELEGATOR));
+ assertTrue(mNotificationManager.canNotifyAsPackage(TEST_APP));
final Intent revokeIntent = new Intent();
- revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+ revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(revokeIntent);
Thread.sleep(500);
@@ -2699,7 +2790,7 @@
Notification n = new Notification.Builder(mContext, "channel")
.setSmallIcon(android.R.id.icon)
.build();
- mNotificationManager.notifyAsPackage(DELEGATOR, "tag", 0, n);
+ mNotificationManager.notifyAsPackage(TEST_APP, "tag", 0, n);
fail("Should not be able to post as a delegate when permission revoked");
} catch (SecurityException e) {
// yay
@@ -2786,44 +2877,274 @@
mNotificationManager.shouldHideSilentStatusBarIcons();
}
- public void testMatchesCallFilter() throws Exception {
+ public void testMatchesCallFilter_noPermissions() {
+ // make sure we definitely don't have contacts access
+ boolean hadReadPerm = hasReadContactsPermission(TEST_APP);
+ try {
+ toggleReadContactsPermission(TEST_APP, false);
+
+ // start an activity that has no permissions, which will run matchesCallFilter on
+ // a meaningless uri. The result code indicates whether or not the method call was
+ // permitted.
+ final Intent mcfIntent = new Intent();
+ mcfIntent.setPackage(TEST_APP);
+ mcfIntent.setClassName(TEST_APP, MATCHES_CALL_FILTER_CLASS);
+ GetResultActivity grActivity = setUpGetResultActivity();
+ grActivity.startActivityForResult(mcfIntent, REQUEST_CODE);
+ UiDevice.getInstance(mInstrumentation).waitForIdle();
+
+ // with no permissions, this call should not have been permitted
+ GetResultActivity.Result result = grActivity.getResult();
+ assertEquals(REQUEST_CODE, result.requestCode);
+ assertEquals(MATCHES_CALL_FILTER_NOT_PERMITTED, result.resultCode);
+ grActivity.finishActivity(REQUEST_CODE);
+ } finally {
+ toggleReadContactsPermission(TEST_APP, hadReadPerm);
+ }
+ }
+
+ public void testMatchesCallFilter_listenerPermissionOnly() throws Exception {
+ boolean hadReadPerm = hasReadContactsPermission(TEST_APP);
+ // minimal listener service so that it can be given listener permissions
+ final ComponentName listenerComponent =
+ new ComponentName(TEST_APP, MINIMAL_LISTENER_CLASS);
+ try {
+ // make surethat we don't for some reason have contacts access
+ toggleReadContactsPermission(TEST_APP, false);
+
+ // grant the notification app package notification listener access;
+ // give it time to succeed
+ toggleExternalListenerAccess(listenerComponent, true);
+ Thread.sleep(500);
+
+ // set up & run intent
+ final Intent mcfIntent = new Intent();
+ mcfIntent.setPackage(TEST_APP);
+ mcfIntent.setClassName(TEST_APP, MATCHES_CALL_FILTER_CLASS);
+ GetResultActivity grActivity = setUpGetResultActivity();
+ grActivity.startActivityForResult(mcfIntent, REQUEST_CODE);
+ UiDevice.getInstance(mInstrumentation).waitForIdle();
+
+ // with just listener permissions, this call should have been permitted
+ GetResultActivity.Result result = grActivity.getResult();
+ assertEquals(REQUEST_CODE, result.requestCode);
+ assertEquals(MATCHES_CALL_FILTER_PERMITTED, result.resultCode);
+ grActivity.finishActivity(REQUEST_CODE);
+ } finally {
+ // clean up listener access, reset read contacts access
+ toggleExternalListenerAccess(listenerComponent, false);
+ toggleReadContactsPermission(TEST_APP, hadReadPerm);
+ }
+ }
+
+ public void testMatchesCallFilter_contactsPermissionOnly() throws Exception {
+ // grant the notification app package contacts read access
+ boolean hadReadPerm = hasReadContactsPermission(TEST_APP);
+ try {
+ toggleReadContactsPermission(TEST_APP, true);
+
+ // set up & run intent
+ final Intent mcfIntent = new Intent();
+ mcfIntent.setPackage(TEST_APP);
+ mcfIntent.setClassName(TEST_APP, MATCHES_CALL_FILTER_CLASS);
+ GetResultActivity grActivity = setUpGetResultActivity();
+ grActivity.startActivityForResult(mcfIntent, REQUEST_CODE);
+ UiDevice.getInstance(mInstrumentation).waitForIdle();
+
+ // with just contacts read permissions, this call should have been permitted
+ GetResultActivity.Result result = grActivity.getResult();
+ assertEquals(REQUEST_CODE, result.requestCode);
+ assertEquals(MATCHES_CALL_FILTER_PERMITTED, result.resultCode);
+ grActivity.finishActivity(REQUEST_CODE);
+ } finally {
+ // clean up contacts access
+ toggleReadContactsPermission(TEST_APP, hadReadPerm);
+ }
+ }
+
+ public void testMatchesCallFilter_zenOff() throws Exception {
+ // zen mode is not on so nothing is filtered; matchesCallFilter should always pass
+ toggleNotificationPolicyAccess(mContext.getPackageName(),
+ InstrumentationRegistry.getInstrumentation(), true);
+ int origFilter = mNotificationManager.getCurrentInterruptionFilter();
+ try {
+ // allowed from anyone: nothing is filtered, and make sure change went through
+ mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_ALL);
+ assertExpectedDndState(INTERRUPTION_FILTER_ALL);
+
+ // create a phone URI from which to receive a call
+ Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode("+16175551212"));
+ assertTrue(mNotificationManager.matchesCallFilter(phoneUri));
+ } finally {
+ mNotificationManager.setInterruptionFilter(origFilter);
+ }
+ }
+
+ public void testMatchesCallFilter_noCallInterruptions() throws Exception {
+ // when no call interruptions are allowed at all, or only alarms, matchesCallFilter
+ // should always fail
+ toggleNotificationPolicyAccess(mContext.getPackageName(),
+ InstrumentationRegistry.getInstrumentation(), true);
+ int origFilter = mNotificationManager.getCurrentInterruptionFilter();
+ Policy origPolicy = mNotificationManager.getNotificationPolicy();
+ try {
+ // create a phone URI from which to receive a call
+ Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode("+16175551212"));
+
+ // no interruptions allowed at all
+ mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_NONE);
+ assertExpectedDndState(INTERRUPTION_FILTER_NONE);
+ assertFalse(mNotificationManager.matchesCallFilter(phoneUri));
+
+ // only alarms
+ mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS);
+ assertExpectedDndState(INTERRUPTION_FILTER_ALARMS);
+ assertFalse(mNotificationManager.matchesCallFilter(phoneUri));
+
+ mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+ PRIORITY_CATEGORY_MESSAGES, 0, 0));
+ // turn on manual DND
+ mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
+ assertExpectedDndState(INTERRUPTION_FILTER_PRIORITY);
+ assertFalse(mNotificationManager.matchesCallFilter(phoneUri));
+ } finally {
+ mNotificationManager.setInterruptionFilter(origFilter);
+ mNotificationManager.setNotificationPolicy(origPolicy);
+ }
+ }
+
+ public void testMatchesCallFilter_someCallers() throws Exception {
+ // zen mode is active; check various configurations where some calls, but not all calls,
+ // are allowed
+ toggleNotificationPolicyAccess(mContext.getPackageName(),
+ InstrumentationRegistry.getInstrumentation(), true);
+ int origFilter = mNotificationManager.getCurrentInterruptionFilter();
+ Policy origPolicy = mNotificationManager.getNotificationPolicy();
+ Uri aliceUri = null;
+ Uri bobUri = null;
+ try {
+ // set up phone numbers: one starred, one regular, one unknown number
+ // starred contact from whom to receive a call
+ insertSingleContact(ALICE, ALICE_PHONE, ALICE_EMAIL, true);
+ aliceUri = lookupContact(ALICE_PHONE);
+
+ // non-starred contact from whom to also receive a call
+ insertSingleContact(BOB, BOB_PHONE, BOB_EMAIL, false);
+ bobUri = lookupContact(BOB_PHONE);
+
+ // non-contact phone URI
+ Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode("+16175555656"));
+
+ // set up: any contacts are allowed to call.
+ mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+ PRIORITY_CATEGORY_CALLS,
+ NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS, 0));
+
+ // turn on manual DND
+ mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
+ assertExpectedDndState(INTERRUPTION_FILTER_PRIORITY);
+
+ // in this case Alice and Bob should get through but not the unknown number.
+ assertTrue(mNotificationManager.matchesCallFilter(aliceUri));
+ assertTrue(mNotificationManager.matchesCallFilter(bobUri));
+ assertFalse(mNotificationManager.matchesCallFilter(phoneUri));
+
+ // set up: only starred contacts are allowed to call.
+ mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+ PRIORITY_CATEGORY_CALLS,
+ NotificationManager.Policy.PRIORITY_SENDERS_STARRED, 0));
+ assertExpectedDndState(INTERRUPTION_FILTER_PRIORITY);
+
+ // now only Alice should be allowed to get through
+ assertTrue(mNotificationManager.matchesCallFilter(aliceUri));
+ assertFalse(mNotificationManager.matchesCallFilter(bobUri));
+ assertFalse(mNotificationManager.matchesCallFilter(phoneUri));
+ } finally {
+ mNotificationManager.setInterruptionFilter(origFilter);
+ mNotificationManager.setNotificationPolicy(origPolicy);
+ if (aliceUri != null) {
+ // delete the contact
+ deleteSingleContact(aliceUri);
+ }
+ if (bobUri != null) {
+ deleteSingleContact(bobUri);
+ }
+ }
+ }
+
+ public void testMatchesCallFilter_repeatCallers() throws Exception {
+ // if repeat callers are allowed, an unknown number calling twice should go through
+ toggleNotificationPolicyAccess(mContext.getPackageName(),
+ InstrumentationRegistry.getInstrumentation(), true);
+ int origFilter = mNotificationManager.getCurrentInterruptionFilter();
+ Policy origPolicy = mNotificationManager.getNotificationPolicy();
+ long startTime = System.currentTimeMillis();
+ try {
+ // create a phone URI from which to receive a call
+ Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode("+16175551212"));
+
+ mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+ PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0));
+ // turn on manual DND
+ mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
+ assertExpectedDndState(INTERRUPTION_FILTER_PRIORITY);
+
+ // not a repeat caller yet, so it shouldn't be allowed
+ assertFalse(mNotificationManager.matchesCallFilter(phoneUri));
+
+ // register a call from this number, then cancel the notification, which is when
+ // a call is actually recorded.
+ sendNotification(1, null, R.drawable.blue, true, phoneUri);
+ cancelAndPoll(1);
+
+ // now this number should count as a repeat caller
+ assertTrue(mNotificationManager.matchesCallFilter(phoneUri));
+ } finally {
+ mNotificationManager.setInterruptionFilter(origFilter);
+ mNotificationManager.setNotificationPolicy(origPolicy);
+
+ // make sure we clean up the recent call, otherwise future runs of this will fail
+ // and we'll have a fake call still kicking around somewhere.
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ mNotificationManager.cleanUpCallersAfter(startTime));
+ }
+ }
+
+ public void testMatchesCallFilter_allCallers() throws Exception {
// allow all callers
toggleNotificationPolicyAccess(mContext.getPackageName(),
InstrumentationRegistry.getInstrumentation(), true);
+ int origFilter = mNotificationManager.getCurrentInterruptionFilter();
Policy origPolicy = mNotificationManager.getNotificationPolicy();
Uri aliceUri = null;
try {
NotificationManager.Policy currPolicy = mNotificationManager.getNotificationPolicy();
NotificationManager.Policy newPolicy = new NotificationManager.Policy(
NotificationManager.Policy.PRIORITY_CATEGORY_CALLS
- | NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS,
+ | PRIORITY_CATEGORY_REPEAT_CALLERS,
NotificationManager.Policy.PRIORITY_SENDERS_ANY,
currPolicy.priorityMessageSenders,
currPolicy.suppressedVisualEffects);
mNotificationManager.setNotificationPolicy(newPolicy);
-
- // add a contact
- String ALICE = "Alice";
- String ALICE_PHONE = "+16175551212";
- String ALICE_EMAIL = "alice@_foo._bar";
+ mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
+ assertExpectedDndState(INTERRUPTION_FILTER_PRIORITY);
insertSingleContact(ALICE, ALICE_PHONE, ALICE_EMAIL, false);
- final Bundle peopleExtras = new Bundle();
- ArrayList<Person> personList = new ArrayList<>();
aliceUri = lookupContact(ALICE_PHONE);
- personList.add(new Person.Builder().setUri(aliceUri.toString()).build());
- peopleExtras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, personList);
- SystemUtil.runWithShellPermissionIdentity(() ->
- assertTrue(mNotificationManager.matchesCallFilter(peopleExtras)));
+ assertTrue(mNotificationManager.matchesCallFilter(aliceUri));
} finally {
+ mNotificationManager.setInterruptionFilter(origFilter);
mNotificationManager.setNotificationPolicy(origPolicy);
if (aliceUri != null) {
// delete the contact
deleteSingleContact(aliceUri);
}
}
-
}
/* Confirm that the optional methods of TestNotificationListener still exist and
@@ -3239,7 +3560,8 @@
}
mListener.cancelNotifications(new String[]{ sbn.getKey() });
- if (!checkNotificationExistence(notificationId, /*shouldExist=*/ false)) {
+ if (getCancellationReason(sbn.getKey())
+ != NotificationListenerService.REASON_LISTENER_CANCEL) {
fail("Failed to cancel notification id=" + notificationId);
}
}
diff --git a/tests/app/src/android/app/cts/RequestTileServiceAddTest.kt b/tests/app/src/android/app/cts/RequestTileServiceAddTest.kt
new file mode 100644
index 0000000..e7924bc
--- /dev/null
+++ b/tests/app/src/android/app/cts/RequestTileServiceAddTest.kt
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Manifest.permission.STATUS_BAR
+import android.app.Activity
+import android.app.ActivityManager
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+import android.app.Instrumentation
+import android.app.StatusBarManager
+import android.app.stubs.MockActivity
+import android.app.stubs.NotExportedTestTileService
+import android.app.stubs.TestTileService
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.drawable.Icon
+import android.service.quicksettings.TileService
+import androidx.test.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compatibility.common.util.SystemUtil
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
+
+/**
+ * Test that the request fails in all the expected ways.
+ *
+ * These tests are for [StatusBarManager.requestAddTileService].
+ */
+@RunWith(AndroidJUnit4::class)
+class RequestTileServiceAddTest {
+
+ companion object {
+ private const val LABEL = "label"
+ private const val TIME_OUT_SECONDS = 5L
+ }
+
+ private lateinit var statusBarService: StatusBarManager
+ private lateinit var context: Context
+ private lateinit var icon: Icon
+ private lateinit var consumer: StoreIntConsumer
+ private lateinit var instrumentation: Instrumentation
+ private val executor = MoreExecutors.directExecutor()
+ private lateinit var latch: CountDownLatch
+
+ @Before
+ fun setUp() {
+ Assume.assumeTrue(TileService.isQuickSettingsSupported())
+
+ instrumentation = InstrumentationRegistry.getInstrumentation()
+ context = instrumentation.getTargetContext()
+ statusBarService = context.getSystemService(StatusBarManager::class.java)!!
+
+ icon = Icon.createWithResource(context, R.drawable.ic_android)
+ latch = CountDownLatch(1)
+ consumer = StoreIntConsumer(latch)
+ }
+
+ @Test
+ fun testRequestBadPackageFails() {
+ val componentName = ComponentName("test_pkg", "test_cls")
+
+ statusBarService.requestAddTileService(
+ componentName,
+ LABEL,
+ icon,
+ executor,
+ consumer
+ )
+
+ latch.await(TIME_OUT_SECONDS, TimeUnit.SECONDS)
+
+ assertThat(consumer.result)
+ .isEqualTo(StatusBarManager.TILE_ADD_REQUEST_ERROR_MISMATCHED_PACKAGE)
+ }
+
+ @Test
+ fun testRequestBadComponentName() {
+ val componentName = ComponentName(context, "test_cls")
+ statusBarService.requestAddTileService(
+ componentName,
+ LABEL,
+ icon,
+ executor,
+ consumer
+ )
+
+ latch.await(TIME_OUT_SECONDS, TimeUnit.SECONDS)
+
+ assertThat(consumer.result).isEqualTo(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT)
+ }
+
+ @Test
+ fun testDisabledComponent() {
+ val componentName = TestTileService.getComponentName()
+ context.packageManager.setComponentEnabledSetting(
+ componentName,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.SYNCHRONOUS or PackageManager.DONT_KILL_APP
+ )
+
+ statusBarService.requestAddTileService(
+ componentName,
+ LABEL,
+ icon,
+ executor,
+ consumer
+ )
+
+ latch.await(TIME_OUT_SECONDS, TimeUnit.SECONDS)
+
+ assertThat(consumer.result).isEqualTo(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT)
+
+ // Cleanup
+ context.packageManager.setComponentEnabledSetting(
+ componentName,
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.SYNCHRONOUS or PackageManager.DONT_KILL_APP
+ )
+ }
+
+ @Test
+ fun testNotExportedComponent() {
+ val componentName = NotExportedTestTileService.getComponentName()
+
+ statusBarService.requestAddTileService(
+ componentName,
+ LABEL,
+ icon,
+ executor,
+ consumer
+ )
+
+ latch.await(TIME_OUT_SECONDS, TimeUnit.SECONDS)
+
+ assertThat(consumer.result).isEqualTo(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT)
+ }
+
+ @Test
+ fun testAppNotInForeground() {
+ // This test is never run in foreground, so it's a good candidate for testing this
+ val componentName = TestTileService.getComponentName()
+ val activityManager = context.getSystemService(ActivityManager::class.java)!!
+ assertThat(activityManager.getPackageImportance(context.packageName))
+ .isNotEqualTo(IMPORTANCE_FOREGROUND)
+
+ statusBarService.requestAddTileService(
+ componentName,
+ LABEL,
+ icon,
+ executor,
+ consumer
+ )
+
+ latch.await(TIME_OUT_SECONDS, TimeUnit.SECONDS)
+
+ assertThat(consumer.result)
+ .isEqualTo(StatusBarManager.TILE_ADD_REQUEST_ERROR_APP_NOT_IN_FOREGROUND)
+ }
+
+ @Test
+ fun testTwoSimultaneousRequests() {
+ // We need an activity in the foreground for the first request to not be denied
+ val activity = setUpForActivity()
+ val componentName = TestTileService.getComponentName()
+
+ statusBarService.requestAddTileService(
+ componentName,
+ LABEL,
+ icon,
+ executor,
+ {}
+ )
+
+ Thread.sleep(TimeUnit.SECONDS.toMillis(TIME_OUT_SECONDS))
+
+ statusBarService.requestAddTileService(
+ componentName,
+ LABEL,
+ icon,
+ executor,
+ consumer
+ )
+
+ latch.await(TIME_OUT_SECONDS, TimeUnit.SECONDS)
+ assertThat(consumer.result)
+ .isEqualTo(StatusBarManager.TILE_ADD_REQUEST_ERROR_REQUEST_IN_PROGRESS)
+
+ SystemUtil.callWithShellPermissionIdentity(
+ { statusBarService.cancelRequestAddTile(componentName.packageName) },
+ STATUS_BAR
+ )
+
+ activity.finish()
+ }
+
+ private fun setUpForActivity(): Activity {
+ val intent = Intent(context, MockActivity::class.java)
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ val activity = instrumentation.startActivitySync(intent)
+ instrumentation.waitForIdleSync()
+ return activity
+ }
+
+ private class StoreIntConsumer(private val latch: CountDownLatch) : Consumer<Int> {
+ var result: Int? = null
+
+ override fun accept(t: Int) {
+ result = t
+ latch.countDown()
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/app/src/android/app/cts/ServiceTest.java b/tests/app/src/android/app/cts/ServiceTest.java
index 2d18509..ee3c06f 100644
--- a/tests/app/src/android/app/cts/ServiceTest.java
+++ b/tests/app/src/android/app/cts/ServiceTest.java
@@ -16,6 +16,9 @@
package android.app.cts;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
+import static android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS;
import static android.app.stubs.LocalForegroundService.COMMAND_START_FOREGROUND;
import static android.app.stubs.LocalForegroundService.COMMAND_START_FOREGROUND_DEFER_NOTIFICATION;
import static android.app.stubs.LocalForegroundService.COMMAND_STOP_FOREGROUND_DETACH_NOTIFICATION;
@@ -53,6 +56,8 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
@@ -698,6 +703,7 @@
protected void setUp() throws Exception {
super.setUp();
mContext = getContext();
+ PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS);
mLocalService = new Intent(mContext, LocalService.class);
mExternalService = new Intent();
mExternalService.setComponent(ComponentName.unflattenFromString(EXTERNAL_SERVICE_COMPONENT));
@@ -745,6 +751,15 @@
}
mBackgroundThread = null;
mBackgroundThreadExecutor = null;
+ // Use test API to prevent PermissionManager from killing the test process when revoking
+ // permission.
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mContext.getSystemService(PermissionManager.class)
+ .revokePostNotificationPermissionWithoutKillForTest(
+ mContext.getPackageName(),
+ Process.myUserHandle().getIdentifier()),
+ REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
+ REVOKE_RUNTIME_PERMISSIONS);
}
private class MockBinder extends Binder {
diff --git a/tests/app/src/android/app/cts/StatusBarManagerTest.java b/tests/app/src/android/app/cts/StatusBarManagerTest.java
index 9995099..7c4f6dd 100644
--- a/tests/app/src/android/app/cts/StatusBarManagerTest.java
+++ b/tests/app/src/android/app/cts/StatusBarManagerTest.java
@@ -18,6 +18,8 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
@@ -40,7 +42,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class StatusBarManagerTest {
- private static final String PERMISSION_STATUS_BAR = "android.permission.STATUS_BAR";
+ private static final String PERMISSION_STATUS_BAR = android.Manifest.permission.STATUS_BAR;
private StatusBarManager mStatusBarManager;
private Context mContext;
@@ -94,6 +96,7 @@
assertTrue(info.isStatusBarExpansionDisabled());
assertTrue(info.isRecentsDisabled());
assertTrue(info.isSearchDisabled());
+ assertFalse(info.isRotationSuggestionDisabled());
}
/**
@@ -179,4 +182,41 @@
// Nothing thrown, passed
}
+
+ /**
+ * Test StatusBarManager.setNavBarModeOverride(NAV_BAR_MODE_OVERRIDE_KIDS)
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testSetNavBarModeOverride_kids_doesNotThrow() throws Exception {
+ int navBarModeOverrideKids = StatusBarManager.NAV_BAR_MODE_OVERRIDE_KIDS;
+ mStatusBarManager.setNavBarModeOverride(navBarModeOverrideKids);
+
+ assertEquals(mStatusBarManager.getNavBarModeOverride(), navBarModeOverrideKids);
+ }
+
+ /**
+ * Test StatusBarManager.setNavBarModeOverride(NAV_BAR_MODE_OVERRIDE_NONE)
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testSetNavBarModeOverride_none_doesNotThrow() throws Exception {
+ int navBarModeOverrideNone = StatusBarManager.NAV_BAR_MODE_OVERRIDE_NONE;
+ mStatusBarManager.setNavBarModeOverride(navBarModeOverrideNone);
+
+ assertEquals(mStatusBarManager.getNavBarModeOverride(), navBarModeOverrideNone);
+ }
+
+ /**
+ * Test StatusBarManager.setNavBarModeOverride(-1) // invalid input
+ *
+ * @throws Exception
+ */
+ @Test(expected = UnsupportedOperationException.class)
+ public void testSetNavBarModeOverride_invalid_throws() throws Exception {
+ int invalidInput = -1;
+ mStatusBarManager.setNavBarModeOverride(invalidInput);
+ }
}
diff --git a/tests/app/src/android/app/cts/SystemFeaturesTest.java b/tests/app/src/android/app/cts/SystemFeaturesTest.java
index 19dc7a3..b850606 100644
--- a/tests/app/src/android/app/cts/SystemFeaturesTest.java
+++ b/tests/app/src/android/app/cts/SystemFeaturesTest.java
@@ -508,7 +508,7 @@
@Test
public void testTelephonyFeatures() {
if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) ||
- !mPackageManager.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) {
+ !mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELECOM)) {
return;
}
diff --git a/tests/app/src/android/app/cts/TileServiceTest.java b/tests/app/src/android/app/cts/TileServiceTest.java
deleted file mode 100644
index 2509eb7..0000000
--- a/tests/app/src/android/app/cts/TileServiceTest.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR 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 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.AlertDialog;
-import android.app.Dialog;
-import android.app.stubs.TestTileService;
-import android.os.Looper;
-import android.service.quicksettings.Tile;
-import android.service.quicksettings.TileService;
-
-import org.junit.Test;
-
-public class TileServiceTest extends BaseTileServiceTest {
- private final static String TAG = "TileServiceTest";
-
- @Test
- public void testCreateTileService() {
- final TileService tileService = new TileService();
- }
-
- @Test
- public void testListening() throws Exception {
- initializeAndListen();
- }
-
- @Test
- public void testListening_stopped() throws Exception {
- initializeAndListen();
- expandSettings(false);
- waitForListening(false);
- }
-
- @Test
- public void testLocked_deviceNotLocked() throws Exception {
- initializeAndListen();
- assertFalse(mTileService.isLocked());
- }
-
- @Test
- public void testSecure_deviceNotSecure() throws Exception {
- initializeAndListen();
- assertFalse(mTileService.isSecure());
- }
-
- @Test
- public void testTile_hasCorrectIcon() throws Exception {
- initializeAndListen();
- Tile tile = mTileService.getQsTile();
- assertEquals(TestTileService.ICON_ID, tile.getIcon().getResId());
- }
-
- @Test
- public void testTile_hasCorrectSubtitle() throws Exception {
- initializeAndListen();
-
- Tile tile = mTileService.getQsTile();
- tile.setSubtitle("test_subtitle");
- tile.updateTile();
- assertEquals("test_subtitle", tile.getSubtitle());
- }
-
- @Test
- public void testTile_hasCorrectStateDescription() throws Exception {
- initializeAndListen();
-
- Tile tile = mTileService.getQsTile();
- tile.setStateDescription("test_stateDescription");
- tile.updateTile();
- assertEquals("test_stateDescription", tile.getStateDescription());
- }
-
- @Test
- public void testShowDialog() throws Exception {
- Looper.prepare();
- Dialog dialog = new AlertDialog.Builder(mContext).create();
- initializeAndListen();
- clickTile(TestTileService.getComponentName().flattenToString());
- waitForClick();
-
- mTileService.showDialog(dialog);
-
- assertTrue(dialog.isShowing());
- dialog.dismiss();
- }
-
- @Test
- public void testUnlockAndRun_phoneIsUnlockedActivityIsRun() throws Exception {
- initializeAndListen();
- assertFalse(mTileService.isLocked());
-
- TestRunnable testRunnable = new TestRunnable();
-
- mTileService.unlockAndRun(testRunnable);
- Thread.sleep(100); // wait for activity to run
- waitForRun(testRunnable);
- }
-
- @Test
- public void testTileInDumpAndHasState() throws Exception {
- initializeAndListen();
-
- final CharSequence tileLabel = mTileService.getQsTile().getLabel();
-
- final String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
- final String line = findLine(dumpLines, tileLabel);
- assertNotNull(line);
- assertTrue(line.trim().startsWith("State")); // Not BooleanState
- }
-
- private void clickTile(String componentName) throws Exception {
- executeShellCommand(" cmd statusbar click-tile " + componentName);
- }
-
- /**
- * Waits for the TileService to receive the clicked event. If it times out it fails the test.
- * @throws InterruptedException
- */
- private void waitForClick() throws InterruptedException {
- int ct = 0;
- while (!TestTileService.hasBeenClicked() && (ct++ < CHECK_RETRIES)) {
- Thread.sleep(CHECK_DELAY);
- }
- assertTrue(TestTileService.hasBeenClicked());
- }
-
- /**
- * Waits for the runnable to be run. If it times out it fails the test.
- * @throws InterruptedException
- */
- private void waitForRun(TestRunnable t) throws InterruptedException {
- int ct = 0;
- while (!t.hasRan && (ct++ < CHECK_RETRIES)) {
- Thread.sleep(CHECK_DELAY);
- }
- assertTrue(t.hasRan);
- }
-
- @Override
- protected void waitForListening(boolean state) throws InterruptedException {
- int ct = 0;
- while (TestTileService.isListening() != state && (ct++ < CHECK_RETRIES)) {
- Thread.sleep(CHECK_DELAY);
- }
- assertEquals(state, TestTileService.isListening());
- }
-
- /**
- * Waits for the TileService to be in the expected connected state. If it times out, it fails
- * the test
- * @param state desired connected state
- * @throws InterruptedException
- */
- @Override
- protected void waitForConnected(boolean state) throws InterruptedException {
- int ct = 0;
- while (TestTileService.isConnected() != state && (ct++ < CHECK_RETRIES)) {
- Thread.sleep(CHECK_DELAY);
- }
- assertEquals(state, TestTileService.isConnected());
- }
-
- @Override
- protected String getTag() {
- return TAG;
- }
-
- @Override
- protected String getComponentName() {
- return TestTileService.getComponentName().flattenToString();
- }
-
- @Override
- protected TileService getTileServiceInstance() {
- return TestTileService.getInstance();
- }
-
- class TestRunnable implements Runnable {
- boolean hasRan = false;
-
- @Override
- public void run() {
- hasRan = true;
- }
- }
-}
diff --git a/tests/app/src/android/app/cts/UiModeManagerTest.java b/tests/app/src/android/app/cts/UiModeManagerTest.java
index bf53bd9..ddb9c77 100644
--- a/tests/app/src/android/app/cts/UiModeManagerTest.java
+++ b/tests/app/src/android/app/cts/UiModeManagerTest.java
@@ -15,11 +15,23 @@
*/
package android.app.cts;
+import static android.app.UiModeManager.MODE_NIGHT_AUTO;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
+import static android.app.UiModeManager.MODE_NIGHT_NO;
+import static android.app.UiModeManager.MODE_NIGHT_YES;
+
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
import android.Manifest;
import android.app.UiAutomation;
import android.app.UiModeManager;
@@ -56,19 +68,37 @@
private static final long WAIT_TIME_INCR_MS = 100;
private UiModeManager mUiModeManager;
+ private boolean mHasModifiedNightModePermissionAcquired = false;
@Override
protected void setUp() throws Exception {
super.setUp();
mUiModeManager = (UiModeManager) getContext().getSystemService(Context.UI_MODE_SERVICE);
assertNotNull(mUiModeManager);
- // reset nightMode
- setNightMode(UiModeManager.MODE_NIGHT_YES);
- setNightMode(UiModeManager.MODE_NIGHT_NO);
+ resetNightMode();
// Make sure automotive projection is not set by this package at the beginning of the test.
releaseAutomotiveProjection();
}
+ @Override
+ protected void tearDown() throws Exception {
+ resetNightMode();
+ }
+
+ private void resetNightMode() {
+ try {
+ if (!mHasModifiedNightModePermissionAcquired) {
+ acquireModifyNightModePermission();
+ }
+ mUiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
+ mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
+ false /* active */);
+ } finally {
+ getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+ mHasModifiedNightModePermissionAcquired = false;
+ }
+ }
+
public void testUiMode() throws Exception {
if (isAutomotive()) {
Log.i(TAG, "testUiMode automotive");
@@ -148,6 +178,184 @@
assertStoredNightModeSetting(UiModeManager.MODE_NIGHT_AUTO);
}
+ public void testSetNightModeCustomType_noPermission_bedtime_shouldThrow() {
+ assertThrows(SecurityException.class,
+ () -> mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME));
+ }
+
+ public void testSetNightModeCustomType_bedtime_shouldPersist() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+ assertThat(mUiModeManager.getNightMode()).isEqualTo(MODE_NIGHT_CUSTOM);
+ assertThat(mUiModeManager.getNightModeCustomType())
+ .isEqualTo(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+ }
+
+ public void testSetNightModeCustomType_schedule_shouldPersist() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_SCHEDULE);
+
+ assertThat(mUiModeManager.getNightMode()).isEqualTo(MODE_NIGHT_CUSTOM);
+ assertThat(mUiModeManager.getNightModeCustomType())
+ .isEqualTo(MODE_NIGHT_CUSTOM_TYPE_SCHEDULE);
+ }
+
+ public void testGetNightModeCustomType_nightModeNo_shouldReturnUnknown() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightMode(MODE_NIGHT_NO);
+
+ assertThat(mUiModeManager.getNightModeCustomType())
+ .isEqualTo(MODE_NIGHT_CUSTOM_TYPE_UNKNOWN);
+ }
+
+ public void testGetNightModeCustomType_nightModeYes_shouldReturnUnknown() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightMode(MODE_NIGHT_YES);
+
+ assertThat(mUiModeManager.getNightModeCustomType())
+ .isEqualTo(MODE_NIGHT_CUSTOM_TYPE_UNKNOWN);
+ }
+
+ public void testGetNightModeCustomType_nightModeAuto_shouldReturnUnknown() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightMode(MODE_NIGHT_AUTO);
+
+ assertThat(mUiModeManager.getNightModeCustomType())
+ .isEqualTo(MODE_NIGHT_CUSTOM_TYPE_UNKNOWN);
+ }
+
+ public void testGetNightModeCustomType_nightModeCustom_shouldReturnScheduleAsDefault() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightMode(MODE_NIGHT_CUSTOM);
+
+ assertThat(mUiModeManager.getNightModeCustomType())
+ .isEqualTo(MODE_NIGHT_CUSTOM_TYPE_SCHEDULE);
+ }
+
+ public void testSetNightModeCustomType_hasPermission_bedtime_shouldNotActivateNightMode() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+ assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_NO);
+ }
+
+ public void testSetNightModeActivatedForCustomMode_noPermission_customTypeBedtime_activateAtBedtime_shouldNotActivateNightMode() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+ getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+ mHasModifiedNightModePermissionAcquired = false;
+
+ assertThat(mUiModeManager.setNightModeActivatedForCustomMode(
+ MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */)).isFalse();
+
+ assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_NO);
+ }
+
+ public void testSetNightModeActivatedForCustomMode_customTypeBedtime_activateAtBedtime_shouldActivateNightMode() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+ assertThat(mUiModeManager.setNightModeActivatedForCustomMode(
+ MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */)).isTrue();
+
+ assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_YES);
+ }
+
+ public void testSetNightModeActivatedForCustomMode_customTypeBedtime_deactivateAtBedtime_shouldDeactivateNightMode() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+ assertThat(mUiModeManager.setNightModeActivatedForCustomMode(
+ MODE_NIGHT_CUSTOM_TYPE_BEDTIME, false /* active */)).isTrue();
+
+ assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_NO);
+ }
+
+ public void testSetNightModeActivatedForCustomMode_modeNo_activateAtBedtime_shouldNotActivateNightMode() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightMode(MODE_NIGHT_NO);
+
+ assertThat(mUiModeManager.setNightModeActivatedForCustomMode(
+ MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */)).isFalse();
+
+ assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_NO);
+ }
+
+ public void testSetNightModeActivatedForCustomMode_modeYes_activateAtBedtime_shouldKeepNightModeActivated() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightMode(MODE_NIGHT_YES);
+
+ assertThat(mUiModeManager.setNightModeActivatedForCustomMode(
+ MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */)).isFalse();
+
+ assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_YES);
+ }
+
+ public void testSetNightModeActivatedForCustomMode_modeAuto_activateAtBedtime_shouldNotActivateNightMode() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightMode(MODE_NIGHT_AUTO);
+
+ assertThat(mUiModeManager.setNightModeActivatedForCustomMode(
+ MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */)).isFalse();
+
+ assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_NO);
+ }
+
+ public void testSetNightModeActivatedForCustomMode_customTypeSchedule_activateAtBedtime_shouldNotActivateNightMode() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_SCHEDULE);
+
+ assertThat(mUiModeManager.setNightModeActivatedForCustomMode(
+ MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */)).isFalse();
+
+ assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_NO);
+ }
+
+ public void testSetNightModeActivatedForCustomMode_modeNo_activateAtBedtime_thenCustomTypeBedtime_shouldActivateNightMode() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightMode(MODE_NIGHT_NO);
+ mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
+ true /* active */);
+
+ mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+ assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_YES);
+ }
+
+ public void testSetNightModeActivatedForCustomMode_modeYes_activateAtBedtime_thenCustomTypeBedtime_shouldActiveNightMode() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightMode(MODE_NIGHT_YES);
+ mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
+ true /* active */);
+
+ mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+ assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_YES);
+ }
+
+ public void testSetNightModeActivatedForCustomMode_modeAuto_activateAtBedtime_thenCustomTypeBedtime_shouldActivateNightMode() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightMode(MODE_NIGHT_AUTO);
+ mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
+ true /* active */);
+
+ mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+ assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_YES);
+ }
+
+ public void testSetNightModeActivatedForCustomMode_customTypeSchedule_activateAtBedtime_thenCustomTypeBedtime_shouldActivateNightMode() {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_SCHEDULE);
+ mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
+ true /* active */);
+
+ mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+ assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_YES);
+ }
+
public void testNightModeAutoNotPersistedCarMode() {
if (mUiModeManager.isNightModeLocked()) {
Log.i(TAG, "testNightModeAutoNotPersistedCarMode skipped: night mode is locked");
@@ -163,6 +371,18 @@
mUiModeManager.disableCarMode(0);
}
+ public void testNightModeCustomTypeBedtimeNotPersistedInCarMode() throws InterruptedException {
+ acquireModifyNightModePermission();
+ mUiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
+ mUiModeManager.enableCarMode(0);
+
+ // We don't expect users modifing bedtime features when driving.
+ mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+ assertStoredNightModeSetting(UiModeManager.MODE_NIGHT_NO);
+ mUiModeManager.disableCarMode(0);
+ }
+
public void testNightModeInCarModeIsTransient() {
if (mUiModeManager.isNightModeLocked()) {
Log.i(TAG, "testNightModeInCarModeIsTransient skipped: night mode is locked");
@@ -685,4 +905,10 @@
? SettingsUtils.getSecureSettingAsUser(UserHandle.USER_SYSTEM, key)
: SettingsUtils.getSecureSetting(key);
}
+
+ private void acquireModifyNightModePermission() {
+ getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.MODIFY_DAY_NIGHT_MODE);
+ mHasModifiedNightModePermissionAcquired = true;
+ }
}
diff --git a/tests/app/src/android/app/cts/UserHandleTest.java b/tests/app/src/android/app/cts/UserHandleTest.java
index ec7cc40..cb6f18d 100644
--- a/tests/app/src/android/app/cts/UserHandleTest.java
+++ b/tests/app/src/android/app/cts/UserHandleTest.java
@@ -16,27 +16,32 @@
package android.app.cts;
+import static org.junit.Assert.assertSame;
+
import android.os.Parcel;
import android.os.UserHandle;
import android.test.AndroidTestCase;
public class UserHandleTest extends AndroidTestCase {
private static final int TEST_APP_ID = 1234;
+
private static void assertSameUserHandle(int userId) {
assertSame(UserHandle.of(userId), UserHandle.of(userId));
}
public void testOf() {
- for (int i = -1000; i < 100; i++) {
- assertEquals(i, UserHandle.of(i).getIdentifier());
- }
-
- // Ensure common objects are cached.
+ // We test them separately since technically it's possible for these constants to have
+ // different values than the AOSP contains and are out of the [-1000, 1000] range.
assertSameUserHandle(UserHandle.USER_SYSTEM);
assertSameUserHandle(UserHandle.USER_ALL);
assertSameUserHandle(UserHandle.USER_NULL);
- assertSameUserHandle(UserHandle.MIN_SECONDARY_USER_ID);
- assertSameUserHandle(UserHandle.MIN_SECONDARY_USER_ID + 1);
+
+ for (int userId = -1000; userId <= 1000; userId++) {
+ assertEquals(userId, UserHandle.of(userId).getIdentifier());
+
+ // Because of the cache, this should always be true.
+ assertSameUserHandle(userId);
+ }
}
private static void assertParcel(int userId) {
diff --git a/tests/app/src/android/app/cts/WallpaperManagerTest.java b/tests/app/src/android/app/cts/WallpaperManagerTest.java
index 7fbd297..f2f4985 100644
--- a/tests/app/src/android/app/cts/WallpaperManagerTest.java
+++ b/tests/app/src/android/app/cts/WallpaperManagerTest.java
@@ -29,6 +29,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.Manifest;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.app.stubs.R;
@@ -68,6 +69,9 @@
private static final boolean DEBUG = false;
private static final String TAG = "WallpaperManagerTest";
+ private static final long MAX_WAIT_TIME_SECS = 2;
+ private static final long MAX_WAIT_TIME_MS = MAX_WAIT_TIME_SECS * 1000;
+ private static final long WAIT_TIME_INCR_MS = 100;
private WallpaperManager mWallpaperManager;
private Context mContext;
@@ -75,6 +79,7 @@
private BroadcastReceiver mBroadcastReceiver;
private CountDownLatch mCountDownLatch;
private boolean mEnableWcg;
+ private boolean mAcquiredWallpaperDimmingPermission = false;
@Before
public void setUp() throws Exception {
@@ -106,6 +111,23 @@
if (mBroadcastReceiver != null) {
mContext.unregisterReceiver(mBroadcastReceiver);
}
+ try {
+ ensureSetWallpaperDimAmountPermissionIsGranted();
+ mWallpaperManager.setWallpaperDimAmount(0f);
+ assertDimAmountEqualsTo(0f);
+ } finally {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ mAcquiredWallpaperDimmingPermission = false;
+ }
+ }
+
+ private void ensureSetWallpaperDimAmountPermissionIsGranted() {
+ if (!mAcquiredWallpaperDimmingPermission) {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.SET_WALLPAPER_DIM_AMOUNT);
+ mAcquiredWallpaperDimmingPermission = true;
+ }
}
@Test
@@ -403,6 +425,95 @@
}
}
+ @Test
+ public void testGetWallpaperDimAmountWithNoPermission_shouldThrowException() {
+ Assert.assertThrows(SecurityException.class,
+ () -> mWallpaperManager.getWallpaperDimAmount());
+ }
+
+ @Test
+ public void testSetWallpaperDimAmountWithNoPermission_shouldThrowException() {
+ Assert.assertThrows(SecurityException.class,
+ () -> {
+ float dimAmount = 0.5f;
+ mWallpaperManager.setWallpaperDimAmount(dimAmount);
+ assertDimAmountEqualsTo(dimAmount);
+ });
+ }
+
+ @Test
+ public void setWallpaperDimAmount_withinBound_shouldSetDimAmount() {
+ ensureSetWallpaperDimAmountPermissionIsGranted();
+
+ float dimAmount = 0.6f;
+ mWallpaperManager.setWallpaperDimAmount(dimAmount);
+ assertDimAmountEqualsTo(dimAmount);
+
+ // Remove additional dimming and verify that the dim amount is set to 0 again
+ mWallpaperManager.setWallpaperDimAmount(0f);
+ assertDimAmountEqualsTo(0f);
+ }
+
+ @Test
+ public void setWallpaperDimAmountBeyondRange_shouldBeBounded() {
+ ensureSetWallpaperDimAmountPermissionIsGranted();
+
+ // Setting dim amount < 0 should be bounded to lower limit 0.0
+ mWallpaperManager.setWallpaperDimAmount(-1f);
+ assertDimAmountEqualsTo(0f);
+
+ // Setting dim amount > 1 should be bounded to upper limit 1.0
+ mWallpaperManager.setWallpaperDimAmount(1.5f);
+ assertDimAmountEqualsTo(1f);
+ }
+
+ @Test
+ public void setWallpaperDimAmount_changingWallpaperShouldRemainDimmed() throws IOException {
+ ensureSetWallpaperDimAmountPermissionIsGranted();
+
+ float dimAmount = 0.65f;
+ mWallpaperManager.setWallpaperDimAmount(dimAmount);
+ mWallpaperManager.setResource(R.drawable.robot);
+
+ assertDimAmountEqualsTo(dimAmount);
+ }
+
+ @Test
+ public void colorHintsOnDimTest() throws IOException {
+ ensureSetWallpaperDimAmountPermissionIsGranted();
+
+ Bitmap tmpWallpaper = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(tmpWallpaper);
+ canvas.drawColor(Color.WHITE);
+
+ mWallpaperManager.setBitmap(tmpWallpaper);
+
+ WallpaperColors colors = mWallpaperManager
+ .getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
+ int colorHints = colors.getColorHints();
+ // Color hints support dark text on white wallpaper
+ Assert.assertEquals(WallpaperColors.HINT_SUPPORTS_DARK_TEXT,
+ colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT);
+
+ float lowDimAmount = 0.1f;
+ mWallpaperManager.setWallpaperDimAmount(lowDimAmount);
+ assertDimAmountEqualsTo(lowDimAmount);
+ colors = mWallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
+ colorHints = colors.getColorHints();
+ // Color hints still support dark text on white wallpaper that is not dimmed enough
+ Assert.assertEquals(WallpaperColors.HINT_SUPPORTS_DARK_TEXT,
+ colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT);
+
+ float higherDimAmount = 0.7f;
+ mWallpaperManager.setWallpaperDimAmount(higherDimAmount);
+ assertDimAmountEqualsTo(higherDimAmount);
+ colors = mWallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
+ colorHints = colors.getColorHints();
+ // Dimmed white wallpaper does not support dark text
+ Assert.assertNotEquals(WallpaperColors.HINT_SUPPORTS_DARK_TEXT,
+ colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT);
+ }
+
private void assertBitmapDimensions(Bitmap bitmap) {
int maxSize = getMaxTextureSize();
boolean safe = false;
@@ -571,6 +682,20 @@
return spy(new TestableColorListener());
}
+ private void assertDimAmountEqualsTo(float dimAmount) {
+ float storedDimAmount = -1f;
+ for (int i = 0; i < MAX_WAIT_TIME_MS; i += WAIT_TIME_INCR_MS) {
+ storedDimAmount = mWallpaperManager.getWallpaperDimAmount();
+ if (dimAmount == storedDimAmount) break;
+ try {
+ Thread.sleep(WAIT_TIME_INCR_MS);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ Assert.assertEquals(dimAmount, storedDimAmount, /* delta */ 0f);
+ }
+
public class TestableColorListener implements WallpaperManager.OnColorsChangedListener {
@Override
public void onColorsChanged(WallpaperColors colors, int which) {
diff --git a/tests/app/src/android/app/people/cts/PeopleManagerTest.java b/tests/app/src/android/app/people/cts/PeopleManagerTest.java
index 8b67eb4..ac6a52d 100644
--- a/tests/app/src/android/app/people/cts/PeopleManagerTest.java
+++ b/tests/app/src/android/app/people/cts/PeopleManagerTest.java
@@ -16,6 +16,9 @@
package android.app.people.cts;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
+import static android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
import static android.app.people.ConversationStatus.ACTIVITY_GAME;
@@ -38,12 +41,17 @@
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
+import android.os.Process;
import android.os.SystemClock;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
import android.test.AndroidTestCase;
import android.util.ArraySet;
import androidx.test.InstrumentationRegistry;
+import com.android.compatibility.common.util.SystemUtil;
+
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -72,6 +80,7 @@
@Override
protected void setUp() throws Exception {
super.setUp();
+ PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS);
// This will leave a set of channels on the device with each test run.
mId = UUID.randomUUID().toString();
mNotificationManager = mContext.getSystemService(NotificationManager.class);
@@ -105,6 +114,15 @@
mNotificationManager.deleteNotificationChannel(nc.getId());
}
deleteShortcuts();
+ // Use test API to prevent PermissionManager from killing the test process when revoking
+ // permission.
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mContext.getSystemService(PermissionManager.class)
+ .revokePostNotificationPermissionWithoutKillForTest(
+ mContext.getPackageName(),
+ Process.myUserHandle().getIdentifier()),
+ REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
+ REVOKE_RUNTIME_PERMISSIONS);
}
/** Creates a dynamic, longlived, sharing shortcut. Call {@link #deleteShortcuts()} after. */
diff --git a/tests/appsearch/Android.bp b/tests/appsearch/Android.bp
index 8ecb5aa..c17fa34 100644
--- a/tests/appsearch/Android.bp
+++ b/tests/appsearch/Android.bp
@@ -24,6 +24,7 @@
"androidx.test.ext.junit",
"androidx.test.rules",
"compatibility-device-util-axt",
+ "service-appsearch",
"testng",
],
srcs: [
@@ -33,6 +34,7 @@
test_suites: [
"cts",
"general-tests",
+ "mts-appsearch",
],
platform_apis: true,
}
@@ -49,20 +51,19 @@
],
srcs: [
"helper-app/src/**/*.java",
- ":CtsAppSearchTestsAidl"
+ ":CtsAppSearchTestsAidl",
],
test_suites: [
"cts",
- "vts",
- "vts10",
"general-tests",
+ "mts-appsearch",
],
manifest: "helper-app/AndroidManifest.xml",
aaptflags: [
"--rename-manifest-package com.android.cts.appsearch.helper.a",
],
certificate: ":cts-appsearch-helper-cert-a",
- sdk_version: "test_current"
+ sdk_version: "test_current",
}
android_test_helper_app {
@@ -77,25 +78,24 @@
],
srcs: [
"helper-app/src/**/*.java",
- ":CtsAppSearchTestsAidl"
+ ":CtsAppSearchTestsAidl",
],
test_suites: [
"cts",
- "vts",
- "vts10",
"general-tests",
+ "mts-appsearch",
],
manifest: "helper-app/AndroidManifest.xml",
aaptflags: [
"--rename-manifest-package com.android.cts.appsearch.helper.b",
],
certificate: ":cts-appsearch-helper-cert-b",
- sdk_version: "test_current"
+ sdk_version: "test_current",
}
filegroup {
name: "CtsAppSearchTestsAidl",
srcs: [
"aidl/**/*.aidl",
- ]
+ ],
}
diff --git a/tests/appsearch/AndroidTest.xml b/tests/appsearch/AndroidTest.xml
index 9577159..245569d 100644
--- a/tests/appsearch/AndroidTest.xml
+++ b/tests/appsearch/AndroidTest.xml
@@ -32,4 +32,9 @@
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.cts.appsearch" />
</test>
+
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.appsearch" />
+ </object>
</configuration>
diff --git a/tests/appsearch/helper-app/src/com/android/cts/appsearch/helper/AppSearchTestService.java b/tests/appsearch/helper-app/src/com/android/cts/appsearch/helper/AppSearchTestService.java
index c30f625..76ba9f1 100644
--- a/tests/appsearch/helper-app/src/com/android/cts/appsearch/helper/AppSearchTestService.java
+++ b/tests/appsearch/helper-app/src/com/android/cts/appsearch/helper/AppSearchTestService.java
@@ -15,8 +15,8 @@
*/
package com.android.cts.appsearch.helper;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
+import static android.app.appsearch.testutil.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static android.app.appsearch.testutil.AppSearchTestUtils.convertSearchResultsToDocuments;
import android.app.Service;
import android.app.appsearch.AppSearchManager;
@@ -27,14 +27,14 @@
import android.app.appsearch.SearchResultsShim;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.testutil.AppSearchEmail;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
+import android.app.appsearch.testutil.GlobalSearchSessionShimImpl;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import com.android.cts.appsearch.ICommandReceiver;
-import com.android.server.appsearch.testing.AppSearchEmail;
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-import com.android.server.appsearch.testing.GlobalSearchSessionShimImpl;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSchemaMigrationCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSchemaMigrationCtsTest.java
index 2c6e032..20529fe 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSchemaMigrationCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSchemaMigrationCtsTest.java
@@ -17,11 +17,10 @@
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
import androidx.annotation.NonNull;
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-
import com.google.common.util.concurrent.ListenableFuture;
public class AppSearchSchemaMigrationCtsTest extends AppSearchSchemaMigrationCtsTestBase {
diff --git a/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSessionCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSessionCtsTest.java
index 559653cf..2efc5f1 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSessionCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSessionCtsTest.java
@@ -17,13 +17,12 @@
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.ExecutorService;
diff --git a/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSessionPlatformCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSessionPlatformCtsTest.java
index c586d2c..c11d701 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSessionPlatformCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSessionPlatformCtsTest.java
@@ -24,6 +24,8 @@
import android.app.appsearch.AppSearchSessionShim;
import android.app.appsearch.PutDocumentsRequest;
import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.testutil.AppSearchEmail;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
import android.app.usage.StorageStats;
import android.app.usage.StorageStatsManager;
import android.content.Context;
@@ -31,9 +33,6 @@
import androidx.test.core.app.ApplicationProvider;
-import com.android.server.appsearch.testing.AppSearchEmail;
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
diff --git a/tests/appsearch/src/com/android/cts/appsearch/app/GlobalSearchSessionCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/app/GlobalSearchSessionCtsTest.java
index ecdc9c0..3664743 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/app/GlobalSearchSessionCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/app/GlobalSearchSessionCtsTest.java
@@ -18,12 +18,11 @@
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchSessionShim;
import android.app.appsearch.GlobalSearchSessionShim;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
+import android.app.appsearch.testutil.GlobalSearchSessionShimImpl;
import androidx.annotation.NonNull;
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-import com.android.server.appsearch.testing.GlobalSearchSessionShimImpl;
-
import com.google.common.util.concurrent.ListenableFuture;
public class GlobalSearchSessionCtsTest extends GlobalSearchSessionCtsTestBase {
diff --git a/tests/appsearch/src/com/android/cts/appsearch/app/GlobalSearchSessionPlatformCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/app/GlobalSearchSessionPlatformCtsTest.java
index 2d5331d..b1a6511 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/app/GlobalSearchSessionPlatformCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/app/GlobalSearchSessionPlatformCtsTest.java
@@ -16,8 +16,7 @@
package android.app.appsearch.cts.app;
import static android.Manifest.permission.READ_GLOBAL_APP_SEARCH_DATA;
-
-import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static android.app.appsearch.testutil.AppSearchTestUtils.checkIsBatchResultSuccess;
import static com.google.common.truth.Truth.assertThat;
@@ -33,6 +32,9 @@
import android.app.appsearch.SearchResultsShim;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.testutil.AppSearchEmail;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
+import android.app.appsearch.testutil.GlobalSearchSessionShimImpl;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -45,9 +47,6 @@
import com.android.compatibility.common.util.SystemUtil;
import com.android.cts.appsearch.ICommandReceiver;
-import com.android.server.appsearch.testing.AppSearchEmail;
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-import com.android.server.appsearch.testing.GlobalSearchSessionShimImpl;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.BaseEncoding;
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSchemaCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSchemaCtsTest.java
index 6dd9158..4661354 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSchemaCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSchemaCtsTest.java
@@ -23,8 +23,7 @@
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.AppSearchSchema.PropertyConfig;
import android.app.appsearch.AppSearchSchema.StringPropertyConfig;
-
-import com.android.server.appsearch.testing.AppSearchEmail;
+import android.app.appsearch.testutil.AppSearchEmail;
import org.junit.Test;
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSchemaMigrationCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSchemaMigrationCtsTestBase.java
index a87a1ac..af62872 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSchemaMigrationCtsTestBase.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSchemaMigrationCtsTestBase.java
@@ -17,10 +17,9 @@
package android.app.appsearch.cts.app;
import static android.app.appsearch.AppSearchResult.RESULT_NOT_FOUND;
-
-import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.doGet;
+import static android.app.appsearch.testutil.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static android.app.appsearch.testutil.AppSearchTestUtils.convertSearchResultsToDocuments;
+import static android.app.appsearch.testutil.AppSearchTestUtils.doGet;
import static com.google.common.truth.Truth.assertThat;
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSessionCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSessionCtsTestBase.java
index d30d661..f78b2a6 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSessionCtsTestBase.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSessionCtsTestBase.java
@@ -18,11 +18,10 @@
import static android.app.appsearch.AppSearchResult.RESULT_INVALID_SCHEMA;
import static android.app.appsearch.AppSearchResult.RESULT_NOT_FOUND;
-
-import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.doGet;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.retrieveAllSearchResults;
+import static android.app.appsearch.testutil.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static android.app.appsearch.testutil.AppSearchTestUtils.convertSearchResultsToDocuments;
+import static android.app.appsearch.testutil.AppSearchTestUtils.doGet;
+import static android.app.appsearch.testutil.AppSearchTestUtils.retrieveAllSearchResults;
import static com.google.common.truth.Truth.assertThat;
@@ -35,6 +34,7 @@
import android.app.appsearch.AppSearchSchema.PropertyConfig;
import android.app.appsearch.AppSearchSchema.StringPropertyConfig;
import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.Features;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.GetByDocumentIdRequest;
import android.app.appsearch.GetSchemaResponse;
@@ -47,12 +47,11 @@
import android.app.appsearch.SetSchemaRequest;
import android.app.appsearch.StorageInfo;
import android.app.appsearch.exceptions.AppSearchException;
+import android.app.appsearch.testutil.AppSearchEmail;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
-import com.android.server.appsearch.testing.AppSearchEmail;
-
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
@@ -70,8 +69,8 @@
import java.util.concurrent.ExecutorService;
public abstract class AppSearchSessionCtsTestBase {
- private static final String DB_NAME_1 = "";
- private static final String DB_NAME_2 = "testDb2";
+ static final String DB_NAME_1 = "";
+ static final String DB_NAME_2 = "testDb2";
private AppSearchSessionShim mDb1;
private AppSearchSessionShim mDb2;
@@ -1254,6 +1253,17 @@
documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).hasSize(1);
assertThat(documents).containsExactly(inDoc);
+
+ // Query only for non-exist type
+ searchResults =
+ mDb1.search(
+ "body",
+ new SearchSpec.Builder()
+ .addFilterSchemas("nonExistType")
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).isEmpty();
}
@Test
@@ -1347,6 +1357,17 @@
documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).hasSize(1);
assertThat(documents).containsExactly(expectedEmail);
+
+ // Query only for non-exist namespace
+ searchResults =
+ mDb1.search(
+ "body",
+ new SearchSpec.Builder()
+ .addFilterNamespaces("nonExistNamespace")
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).isEmpty();
}
@Test
@@ -2042,7 +2063,7 @@
// Query for the document
SearchResultsShim searchResults =
mDb1.search(
- "foo",
+ "fo",
new SearchSpec.Builder()
.addFilterSchemas("Generic")
.setSnippetCount(1)
@@ -2067,6 +2088,15 @@
assertThat(matchInfo.getSnippetRange())
.isEqualTo(new SearchResult.MatchRange(/*lower=*/ 26, /*upper=*/ 33));
assertThat(matchInfo.getSnippet()).isEqualTo("is foo.");
+
+ if (!mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_MATCH_INFO_SUBMATCH)) {
+ assertThrows(UnsupportedOperationException.class, () -> matchInfo.getSubmatchRange());
+ assertThrows(UnsupportedOperationException.class, () -> matchInfo.getSubmatch());
+ } else {
+ assertThat(matchInfo.getSubmatchRange())
+ .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 29, /*upper=*/ 31));
+ assertThat(matchInfo.getSubmatch()).isEqualTo("fo");
+ }
}
@Test
@@ -2205,6 +2235,15 @@
assertThat(matchInfo.getExactMatchRange())
.isEqualTo(new SearchResult.MatchRange(/*lower=*/ 44, /*upper=*/ 45));
assertThat(matchInfo.getExactMatch()).isEqualTo("は");
+
+ if (!mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_MATCH_INFO_SUBMATCH)) {
+ assertThrows(UnsupportedOperationException.class, () -> matchInfo.getSubmatchRange());
+ assertThrows(UnsupportedOperationException.class, () -> matchInfo.getSubmatch());
+ } else {
+ assertThat(matchInfo.getSubmatchRange())
+ .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 44, /*upper=*/ 45));
+ assertThat(matchInfo.getSubmatch()).isEqualTo("は");
+ }
}
@Test
@@ -2383,6 +2422,50 @@
}
@Test
+ public void testRemoveByQuery_nonExistNamespace() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index documents
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("namespace1", "id1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("foo")
+ .setBody("This is the body of the testPut email")
+ .build();
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("namespace2", "id2")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("bar")
+ .setBody("This is the body of the testPut second email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(email1, email2)
+ .build()));
+
+ // Check the presence of the documents
+ assertThat(doGet(mDb1, "namespace1", "id1")).hasSize(1);
+ assertThat(doGet(mDb1, "namespace2", "id2")).hasSize(1);
+
+ // Delete the email by nonExist namespace.
+ mDb1.remove(
+ "",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+ .addFilterNamespaces("nonExistNamespace")
+ .build())
+ .get();
+ // None of these emails will be deleted.
+ assertThat(doGet(mDb1, "namespace1", "id1")).hasSize(1);
+ assertThat(doGet(mDb1, "namespace2", "id2")).hasSize(1);
+ }
+
+ @Test
public void testRemoveByQuery_packageFilter() throws Exception {
// Schema registration
mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/GenericDocumentCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/GenericDocumentCtsTest.java
index 70f9b48..0dfff00 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/GenericDocumentCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/GenericDocumentCtsTest.java
@@ -332,9 +332,19 @@
public void testDocument_setEmptyValues() {
GenericDocument document =
new GenericDocument.Builder<>("namespace", "id1", "schemaType1")
- .setPropertyBoolean("testKey")
+ .setPropertyBoolean("booleanKey")
+ .setPropertyString("stringKey")
+ .setPropertyBytes("byteKey")
+ .setPropertyDouble("doubleKey")
+ .setPropertyDocument("documentKey")
+ .setPropertyLong("longKey")
.build();
- assertThat(document.getPropertyBooleanArray("testKey")).isEmpty();
+ assertThat(document.getPropertyBooleanArray("booleanKey")).isEmpty();
+ assertThat(document.getPropertyStringArray("stringKey")).isEmpty();
+ assertThat(document.getPropertyBytesArray("byteKey")).isEmpty();
+ assertThat(document.getPropertyDoubleArray("doubleKey")).isEmpty();
+ assertThat(document.getPropertyDocumentArray("documentKey")).isEmpty();
+ assertThat(document.getPropertyLongArray("longKey")).isEmpty();
}
@Test
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/GlobalSearchSessionCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/GlobalSearchSessionCtsTestBase.java
index 7dbe8d9..bcae910 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/GlobalSearchSessionCtsTestBase.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/GlobalSearchSessionCtsTestBase.java
@@ -16,8 +16,8 @@
package android.app.appsearch.cts.app;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
+import static android.app.appsearch.testutil.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static android.app.appsearch.testutil.AppSearchTestUtils.convertSearchResultsToDocuments;
import static com.google.common.truth.Truth.assertThat;
@@ -31,18 +31,21 @@
import android.app.appsearch.GenericDocument;
import android.app.appsearch.GlobalSearchSessionShim;
import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.RemoveByDocumentIdRequest;
import android.app.appsearch.ReportSystemUsageRequest;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResultsShim;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
import android.app.appsearch.exceptions.AppSearchException;
+import android.app.appsearch.observer.DocumentChangeInfo;
+import android.app.appsearch.observer.ObserverSpec;
+import android.app.appsearch.testutil.AppSearchEmail;
+import android.app.appsearch.testutil.TestObserverCallback;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
-import com.android.server.appsearch.testing.AppSearchEmail;
-
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture;
@@ -54,14 +57,20 @@
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
public abstract class GlobalSearchSessionCtsTestBase {
- private AppSearchSessionShim mDb1;
- private static final String DB_NAME_1 = "";
- private AppSearchSessionShim mDb2;
- private static final String DB_NAME_2 = "testDb2";
+ static final String DB_NAME_1 = "";
+ static final String DB_NAME_2 = "testDb2";
- private GlobalSearchSessionShim mGlobalAppSearchManager;
+ private static final Executor EXECUTOR = Executors.newCachedThreadPool();
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+
+ private AppSearchSessionShim mDb1;
+ private AppSearchSessionShim mDb2;
+
+ private GlobalSearchSessionShim mGlobalSearchSession;
protected abstract ListenableFuture<AppSearchSessionShim> createSearchSession(
@NonNull String dbName);
@@ -70,8 +79,6 @@
@Before
public void setUp() throws Exception {
- Context context = ApplicationProvider.getApplicationContext();
-
mDb1 = createSearchSession(DB_NAME_1).get();
mDb2 = createSearchSession(DB_NAME_2).get();
@@ -79,7 +86,7 @@
// addition to tearDown in case a test exited without completing properly.
cleanup();
- mGlobalAppSearchManager = createGlobalSearchSession().get();
+ mGlobalSearchSession = createGlobalSearchSession().get();
}
@After
@@ -95,7 +102,7 @@
private List<GenericDocument> snapshotResults(String queryExpression, SearchSpec spec)
throws Exception {
- SearchResultsShim searchResults = mGlobalAppSearchManager.search(queryExpression, spec);
+ SearchResultsShim searchResults = mGlobalSearchSession.search(queryExpression, spec);
return convertSearchResultsToDocuments(searchResults);
}
@@ -225,7 +232,7 @@
// Set number of results per page is 7.
int pageSize = 7;
SearchResultsShim searchResults =
- mGlobalAppSearchManager.search(
+ mGlobalSearchSession.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
@@ -406,8 +413,7 @@
SearchSpec testPackageSearchSpec =
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
- .addFilterPackageNames(
- ApplicationProvider.getApplicationContext().getPackageName())
+ .addFilterPackageNames(mContext.getPackageName())
.build();
List<GenericDocument> beforeTestPackageDocuments =
snapshotResults("body", testPackageSearchSpec);
@@ -732,7 +738,7 @@
// Query
List<SearchResult> page;
try (SearchResultsShim results =
- mGlobalAppSearchManager.search(
+ mGlobalSearchSession.search(
"",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
@@ -747,7 +753,7 @@
assertThrows(
ExecutionException.class,
() ->
- mGlobalAppSearchManager
+ mGlobalSearchSession
.reportSystemUsage(
new ReportSystemUsageRequest.Builder(
firstResult.getPackageName(),
@@ -767,4 +773,364 @@
.hasMessageThat()
.contains("com.android.cts.appsearch does not have access to report system usage");
}
+
+ @Test
+ public void testRegisterObserver() throws Exception {
+ TestObserverCallback observer = new TestObserverCallback();
+
+ // Register observer. Note: the type does NOT exist yet!
+ mGlobalSearchSession.addObserver(
+ mContext.getPackageName(),
+ new ObserverSpec.Builder().addFilterSchemas(AppSearchEmail.SCHEMA_TYPE).build(),
+ EXECUTOR,
+ observer);
+
+ // Index a document
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+ .get();
+ AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
+ checkIsBatchResultSuccess(
+ mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
+
+ // Make sure the notification was received.
+ observer.waitForNotificationCount(1);
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges())
+ .containsExactly(
+ new DocumentChangeInfo(
+ mContext.getPackageName(),
+ DB_NAME_1,
+ "namespace",
+ AppSearchEmail.SCHEMA_TYPE));
+ }
+
+ @Test
+ public void testRegisterObserver_MultiType() throws Exception {
+ TestObserverCallback unfilteredObserver = new TestObserverCallback();
+ TestObserverCallback emailObserver = new TestObserverCallback();
+
+ // Set up the email type in both databases, and the gift type in db1
+ AppSearchSchema giftSchema =
+ new AppSearchSchema.Builder("Gift")
+ .addProperty(
+ new AppSearchSchema.DoublePropertyConfig.Builder("price").build())
+ .build();
+ mDb1.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchEmail.SCHEMA, giftSchema)
+ .build())
+ .get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Register two observers. One has no filters, the other filters on email.
+ mGlobalSearchSession.addObserver(
+ mContext.getPackageName(),
+ new ObserverSpec.Builder().build(),
+ EXECUTOR,
+ unfilteredObserver);
+ mGlobalSearchSession.addObserver(
+ mContext.getPackageName(),
+ new ObserverSpec.Builder().addFilterSchemas(AppSearchEmail.SCHEMA_TYPE).build(),
+ EXECUTOR,
+ emailObserver);
+
+ // Make sure everything is empty
+ assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+ assertThat(unfilteredObserver.getDocumentChanges()).isEmpty();
+ assertThat(emailObserver.getSchemaChanges()).isEmpty();
+ assertThat(emailObserver.getDocumentChanges()).isEmpty();
+
+ // Index some documents
+ AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
+ GenericDocument gift1 =
+ new GenericDocument.Builder<GenericDocument.Builder<?>>("namespace2", "id2", "Gift")
+ .build();
+
+ checkIsBatchResultSuccess(
+ mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
+ checkIsBatchResultSuccess(
+ mDb1.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(email1, gift1)
+ .build()));
+ checkIsBatchResultSuccess(
+ mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
+ checkIsBatchResultSuccess(
+ mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(gift1).build()));
+
+ // Make sure the notification was received.
+ unfilteredObserver.waitForNotificationCount(5);
+ emailObserver.waitForNotificationCount(3);
+
+ assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+ assertThat(unfilteredObserver.getDocumentChanges())
+ .containsExactly(
+ new DocumentChangeInfo(
+ mContext.getPackageName(),
+ DB_NAME_1,
+ "namespace",
+ AppSearchEmail.SCHEMA_TYPE),
+ new DocumentChangeInfo(
+ mContext.getPackageName(),
+ DB_NAME_1,
+ "namespace",
+ AppSearchEmail.SCHEMA_TYPE),
+ new DocumentChangeInfo(
+ mContext.getPackageName(), DB_NAME_1, "namespace2", "Gift"),
+ new DocumentChangeInfo(
+ mContext.getPackageName(),
+ DB_NAME_2,
+ "namespace",
+ AppSearchEmail.SCHEMA_TYPE),
+ new DocumentChangeInfo(
+ mContext.getPackageName(), DB_NAME_1, "namespace2", "Gift"));
+
+ // Check the filtered observer
+ assertThat(emailObserver.getSchemaChanges()).isEmpty();
+ assertThat(emailObserver.getDocumentChanges())
+ .containsExactly(
+ new DocumentChangeInfo(
+ mContext.getPackageName(),
+ DB_NAME_1,
+ "namespace",
+ AppSearchEmail.SCHEMA_TYPE),
+ new DocumentChangeInfo(
+ mContext.getPackageName(),
+ DB_NAME_1,
+ "namespace",
+ AppSearchEmail.SCHEMA_TYPE),
+ new DocumentChangeInfo(
+ mContext.getPackageName(),
+ DB_NAME_2,
+ "namespace",
+ AppSearchEmail.SCHEMA_TYPE));
+ }
+
+ @Test
+ public void testRegisterObserver_removeById() throws Exception {
+ TestObserverCallback unfilteredObserver = new TestObserverCallback();
+ TestObserverCallback emailObserver = new TestObserverCallback();
+
+ // Set up the email and gift types in both databases
+ AppSearchSchema giftSchema =
+ new AppSearchSchema.Builder("Gift")
+ .addProperty(
+ new AppSearchSchema.DoublePropertyConfig.Builder("price").build())
+ .build();
+ mDb1.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchEmail.SCHEMA, giftSchema)
+ .build())
+ .get();
+ mDb2.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchEmail.SCHEMA, giftSchema)
+ .build())
+ .get();
+
+ // Register two observers. One, registered later, has no filters. The other, registered
+ // now, filters on email.
+ mGlobalSearchSession.addObserver(
+ mContext.getPackageName(),
+ new ObserverSpec.Builder().addFilterSchemas(AppSearchEmail.SCHEMA_TYPE).build(),
+ EXECUTOR,
+ emailObserver);
+
+ // Make sure everything is empty
+ assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+ assertThat(unfilteredObserver.getDocumentChanges()).isEmpty();
+ assertThat(emailObserver.getSchemaChanges()).isEmpty();
+ assertThat(emailObserver.getDocumentChanges()).isEmpty();
+
+ // Index some documents
+ AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
+ GenericDocument gift1 =
+ new GenericDocument.Builder<GenericDocument.Builder<?>>("namespace2", "id2", "Gift")
+ .build();
+
+ checkIsBatchResultSuccess(
+ mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
+ checkIsBatchResultSuccess(
+ mDb1.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(email1, gift1)
+ .build()));
+ checkIsBatchResultSuccess(
+ mDb2.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(email1, gift1)
+ .build()));
+ checkIsBatchResultSuccess(
+ mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(gift1).build()));
+
+ // Register the second observer
+ mGlobalSearchSession.addObserver(
+ mContext.getPackageName(),
+ new ObserverSpec.Builder().build(),
+ EXECUTOR,
+ unfilteredObserver);
+
+ // Remove some of the documents.
+ checkIsBatchResultSuccess(
+ mDb1.remove(
+ new RemoveByDocumentIdRequest.Builder("namespace").addIds("id1").build()));
+ checkIsBatchResultSuccess(
+ mDb2.remove(
+ new RemoveByDocumentIdRequest.Builder("namespace2").addIds("id2").build()));
+
+ // Make sure the notification was received. emailObserver should have seen:
+ // +db1:email, +db1:email, +db2:email, -db1:email.
+ // unfilteredObserver (registered later) should have seen:
+ // -db1:email, -db2:gift
+ emailObserver.waitForNotificationCount(4);
+ unfilteredObserver.waitForNotificationCount(2);
+
+ assertThat(emailObserver.getSchemaChanges()).isEmpty();
+ assertThat(emailObserver.getDocumentChanges())
+ .containsExactly(
+ new DocumentChangeInfo(
+ mContext.getPackageName(),
+ DB_NAME_1,
+ "namespace",
+ AppSearchEmail.SCHEMA_TYPE),
+ new DocumentChangeInfo(
+ mContext.getPackageName(),
+ DB_NAME_1,
+ "namespace",
+ AppSearchEmail.SCHEMA_TYPE),
+ new DocumentChangeInfo(
+ mContext.getPackageName(),
+ DB_NAME_2,
+ "namespace",
+ AppSearchEmail.SCHEMA_TYPE),
+ new DocumentChangeInfo(
+ mContext.getPackageName(),
+ DB_NAME_1,
+ "namespace",
+ AppSearchEmail.SCHEMA_TYPE));
+
+ // Check unfilteredObserver
+ assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+ assertThat(unfilteredObserver.getDocumentChanges())
+ .containsExactly(
+ new DocumentChangeInfo(
+ mContext.getPackageName(),
+ DB_NAME_1,
+ "namespace",
+ AppSearchEmail.SCHEMA_TYPE),
+ new DocumentChangeInfo(
+ mContext.getPackageName(), DB_NAME_2, "namespace2", "Gift"));
+ }
+
+ @Test
+ public void testRegisterObserver_removeByQuery() throws Exception {
+ TestObserverCallback unfilteredObserver = new TestObserverCallback();
+ TestObserverCallback emailObserver = new TestObserverCallback();
+
+ // Set up the email and gift types in both databases
+ AppSearchSchema giftSchema =
+ new AppSearchSchema.Builder("Gift")
+ .addProperty(
+ new AppSearchSchema.DoublePropertyConfig.Builder("price").build())
+ .build();
+ mDb1.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchEmail.SCHEMA, giftSchema)
+ .build())
+ .get();
+ mDb2.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchEmail.SCHEMA, giftSchema)
+ .build())
+ .get();
+
+ // Index some documents
+ AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
+ AppSearchEmail email2 =
+ new AppSearchEmail.Builder("namespace", "id2").setBody("caterpillar").build();
+ GenericDocument gift1 =
+ new GenericDocument.Builder<GenericDocument.Builder<?>>("namespace2", "id3", "Gift")
+ .build();
+
+ checkIsBatchResultSuccess(
+ mDb1.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(email1, email2, gift1)
+ .build()));
+ checkIsBatchResultSuccess(
+ mDb2.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(email1, email2, gift1)
+ .build()));
+
+ // Register observers
+ mGlobalSearchSession.addObserver(
+ mContext.getPackageName(),
+ new ObserverSpec.Builder().build(),
+ EXECUTOR,
+ unfilteredObserver);
+ mGlobalSearchSession.addObserver(
+ mContext.getPackageName(),
+ new ObserverSpec.Builder().addFilterSchemas(AppSearchEmail.SCHEMA_TYPE).build(),
+ EXECUTOR,
+ emailObserver);
+
+ // Make sure everything is empty
+ assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+ assertThat(unfilteredObserver.getDocumentChanges()).isEmpty();
+ assertThat(emailObserver.getSchemaChanges()).isEmpty();
+ assertThat(emailObserver.getDocumentChanges()).isEmpty();
+
+ // Remove "cat" emails in db1 and all types in db2
+ mDb1.remove(
+ "cat",
+ new SearchSpec.Builder()
+ .addFilterSchemas(AppSearchEmail.SCHEMA_TYPE)
+ .build())
+ .get();
+ mDb2.remove("", new SearchSpec.Builder().build()).get();
+
+ // Make sure the notification was received. UnfilteredObserver should have seen:
+ // -db1:id2, -db2:id1, -db2:id2, -db2:id3
+ // emailObserver should have seen:
+ // -db1:id2, -db2:id1, -db2:id2
+ // TODO(b/193494000): Notifications are currently grouped by
+ // (package, database, namespace, schema). This causes -db2:id1 and -db2:id2 to be combined
+ // into one notification. Once notifications have IDs, we need to check to make sure all
+ // the individual IDs are reported in the combined notification.
+ unfilteredObserver.waitForNotificationCount(3);
+ emailObserver.waitForNotificationCount(2);
+
+ assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+ assertThat(unfilteredObserver.getDocumentChanges())
+ .containsExactly(
+ new DocumentChangeInfo(
+ mContext.getPackageName(),
+ DB_NAME_1,
+ "namespace",
+ AppSearchEmail.SCHEMA_TYPE),
+ new DocumentChangeInfo(
+ mContext.getPackageName(),
+ DB_NAME_2,
+ "namespace",
+ AppSearchEmail.SCHEMA_TYPE),
+ new DocumentChangeInfo(
+ mContext.getPackageName(), DB_NAME_2, "namespace2", "Gift"));
+
+ // Check emailObserver
+ assertThat(emailObserver.getSchemaChanges()).isEmpty();
+ assertThat(emailObserver.getDocumentChanges())
+ .containsExactly(
+ new DocumentChangeInfo(
+ mContext.getPackageName(),
+ DB_NAME_1,
+ "namespace",
+ AppSearchEmail.SCHEMA_TYPE),
+ new DocumentChangeInfo(
+ mContext.getPackageName(),
+ DB_NAME_2,
+ "namespace",
+ AppSearchEmail.SCHEMA_TYPE));
+ }
}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/PutDocumentsRequestCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/PutDocumentsRequestCtsTest.java
index b42d4a6..bc90e2f 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/PutDocumentsRequestCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/PutDocumentsRequestCtsTest.java
@@ -20,8 +20,7 @@
import static com.google.common.truth.Truth.assertThat;
import android.app.appsearch.PutDocumentsRequest;
-
-import com.android.server.appsearch.testing.AppSearchEmail;
+import android.app.appsearch.testutil.AppSearchEmail;
import com.google.common.collect.ImmutableSet;
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/SearchResultCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/SearchResultCtsTest.java
index 588210b..fa580ab 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/SearchResultCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/SearchResultCtsTest.java
@@ -18,9 +18,10 @@
import static com.google.common.truth.Truth.assertThat;
-import android.app.appsearch.SearchResult;
+import static org.junit.Assert.assertThrows;
-import com.android.server.appsearch.testing.AppSearchEmail;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.testutil.AppSearchEmail;
import org.junit.Test;
@@ -29,10 +30,12 @@
@Test
public void testBuildSearchResult() {
SearchResult.MatchRange exactMatchRange = new SearchResult.MatchRange(3, 8);
+ SearchResult.MatchRange submatchRange = new SearchResult.MatchRange(3, 5);
SearchResult.MatchRange snippetMatchRange = new SearchResult.MatchRange(1, 10);
SearchResult.MatchInfo matchInfo =
new SearchResult.MatchInfo.Builder("body")
.setExactMatchRange(exactMatchRange)
+ .setSubmatchRange(submatchRange)
.setSnippetRange(snippetMatchRange)
.build();
@@ -53,8 +56,10 @@
SearchResult.MatchInfo actualMatchInfo = searchResult.getMatchInfos().get(0);
assertThat(actualMatchInfo.getPropertyPath()).isEqualTo("body");
assertThat(actualMatchInfo.getExactMatchRange()).isEqualTo(exactMatchRange);
+ assertThat(actualMatchInfo.getSubmatchRange()).isEqualTo(submatchRange);
assertThat(actualMatchInfo.getSnippetRange()).isEqualTo(snippetMatchRange);
assertThat(actualMatchInfo.getExactMatch()).isEqualTo("lo Wo");
+ assertThat(actualMatchInfo.getSubmatch()).isEqualTo("lo");
assertThat(actualMatchInfo.getSnippet()).isEqualTo("ello Worl");
assertThat(actualMatchInfo.getFullText()).isEqualTo("Hello World.");
}
@@ -65,4 +70,39 @@
assertThat(matchRange.getStart()).isEqualTo(13);
assertThat(matchRange.getEnd()).isEqualTo(47);
}
+
+ @Test
+ public void testSubmatchRangeNotSet() {
+ AppSearchEmail email =
+ new AppSearchEmail.Builder("namespace1", "id1").setBody("Hello World.").build();
+ SearchResult.MatchInfo matchInfo = new SearchResult.MatchInfo.Builder("body").build();
+ SearchResult searchResult =
+ new SearchResult.Builder("packageName", "databaseName")
+ .setGenericDocument(email)
+ .addMatchInfo(matchInfo)
+ .build();
+
+ // When submatch isn't set, calling getSubmatch and getSubmatchRange should throw.
+ final SearchResult.MatchInfo actualMatchInfoNoSubmatch =
+ searchResult.getMatchInfos().get(0);
+ assertThrows(
+ UnsupportedOperationException.class, () -> actualMatchInfoNoSubmatch.getSubmatch());
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> actualMatchInfoNoSubmatch.getSubmatchRange());
+
+ // When submatch is set, calling getSubmatch and getSubmatchRange should return the
+ // submatch without any problems.
+ SearchResult.MatchRange submatchRange = new SearchResult.MatchRange(3, 5);
+ matchInfo =
+ new SearchResult.MatchInfo.Builder("body").setSubmatchRange(submatchRange).build();
+ searchResult =
+ new SearchResult.Builder("packageName", "databaseName")
+ .setGenericDocument(email)
+ .addMatchInfo(matchInfo)
+ .build();
+ final SearchResult.MatchInfo actualMatchInfo = searchResult.getMatchInfos().get(0);
+ assertThat(actualMatchInfo.getSubmatch()).isEqualTo("lo");
+ assertThat(actualMatchInfo.getSubmatchRange()).isEqualTo(submatchRange);
+ }
}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/SetSchemaRequestCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/SetSchemaRequestCtsTest.java
index 9ae7e60..d9b923d 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/SetSchemaRequestCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/SetSchemaRequestCtsTest.java
@@ -27,10 +27,9 @@
import android.app.appsearch.Migrator;
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.testutil.AppSearchEmail;
import android.util.ArrayMap;
-import com.android.server.appsearch.testing.AppSearchEmail;
-
import org.junit.Test;
import java.util.Arrays;
diff --git a/tests/atv/OWNERS b/tests/atv/OWNERS
new file mode 100644
index 0000000..02fada1
--- /dev/null
+++ b/tests/atv/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 105709
+dwliu@google.com
+jingjiangli@google.com
+zhizhiliu@google.com
diff --git a/tests/atv/SettingsAPI/Android.bp b/tests/atv/SettingsAPI/Android.bp
new file mode 100644
index 0000000..04c7c48
--- /dev/null
+++ b/tests/atv/SettingsAPI/Android.bp
@@ -0,0 +1,47 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsSettingsAPITestCases",
+ defaults: ["cts_defaults"],
+ dex_preopt: {
+ enabled: false,
+ },
+ optimize: {
+ enabled: false,
+ },
+ static_libs: [
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "truth-prebuilt",
+ "TvSettingsAPI"
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+ min_sdk_version: "31",
+}
+
diff --git a/tests/atv/SettingsAPI/AndroidManifest.xml b/tests/atv/SettingsAPI/AndroidManifest.xml
new file mode 100644
index 0000000..6cdb447
--- /dev/null
+++ b/tests/atv/SettingsAPI/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.tv.settings.library.cts">
+ <uses-sdk android:minSdkVersion="31" />
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ </application>
+ <!-- self-instrumenting test package. -->
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="CTS TV Settings API tests"
+ android:targetPackage="com.android.tv.settings.library.cts">
+ </instrumentation>
+</manifest>
diff --git a/tests/atv/SettingsAPI/AndroidTest.xml b/tests/atv/SettingsAPI/AndroidTest.xml
new file mode 100644
index 0000000..0f5f1c6
--- /dev/null
+++ b/tests/atv/SettingsAPI/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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 TV Settings API test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsSettingsAPITestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.tv.settings.library.cts" />
+ </test>
+</configuration>
diff --git a/tests/atv/SettingsAPI/OWNERS b/tests/atv/SettingsAPI/OWNERS
new file mode 100644
index 0000000..bfa0720
--- /dev/null
+++ b/tests/atv/SettingsAPI/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 105709
+dwliu@google.com
+jingjiangli@google.com
+zhizhiliu@google.com
\ No newline at end of file
diff --git a/tests/atv/SettingsAPI/src/com/android/tv/settings/library/cts/ManagerUtilTest.java b/tests/atv/SettingsAPI/src/com/android/tv/settings/library/cts/ManagerUtilTest.java
new file mode 100644
index 0000000..b026a5d
--- /dev/null
+++ b/tests/atv/SettingsAPI/src/com/android/tv/settings/library/cts/ManagerUtilTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.tv.settings.library.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import com.android.tv.settings.library.ManagerUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ManagerUtilTest {
+ // TODO(b/187659818) Add test cases for ManagerUtil.
+ @Test
+ public void testGetChecked() {
+ // TODO(b/187659818) Implement test case for the function
+ // testGetChecked.
+ }
+}
\ No newline at end of file
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index 28d4ebd..58dd553 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -149,6 +149,7 @@
<activity android:name=".activities.SimpleAfterLoginActivity"/>
<activity android:name=".activities.SimpleBeforeLoginActivity"/>
<activity android:name=".activities.NonAutofillableActivity"/>
+ <activity android:name=".activities.ClientSuggestionsActivity"/>
<receiver android:name=".testcore.SelfDestructReceiver"
android:exported="true"
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java
index 0a32981..199e1b1 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java
@@ -291,7 +291,8 @@
(id) -> Helper.findNodeByResourceId(structure, id));
}
} else if (dataset != null) {
- result = dataset.asDataset((id) -> Helper.findNodeByResourceId(structure, id));
+ result = dataset.asDatasetWithNodeResolver(
+ (id) -> Helper.findNodeByResourceId(structure, id));
} else {
throw new IllegalStateException("no dataset or response");
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/ClientSuggestionsActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/ClientSuggestionsActivity.java
new file mode 100644
index 0000000..f69aa02
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/ClientSuggestionsActivity.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.activities;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.ClientAutofillRequestCallback;
+import android.autofillservice.cts.testcore.Helper;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillId;
+
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Activity that has the following fields:
+ *
+ * <ul>
+ * <li>Username EditText (id: username, no input-type)
+ * <li>Password EditText (id: "username", input-type textPassword)
+ * <li>Clear Button
+ * <li>Save Button
+ * <li>Login Button
+ * </ul>
+ */
+public class ClientSuggestionsActivity extends LoginActivity {
+ private static final String TAG = "ClientSuggestionsActivity";
+ private Handler mHandler;
+ ClientAutofillRequestCallback mRequestCallback;
+
+ private final Map<String, AutofillId> mMap = new ArrayMap<>();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mHandler = new Handler(Looper.myLooper());
+
+ mMap.put(Helper.ID_USERNAME, findViewById(R.id.username).getAutofillId());
+ mMap.put(Helper.ID_PASSWORD, findViewById(R.id.password).getAutofillId());
+ mHandler = new Handler(Looper.getMainLooper());
+ Executor executor = new Executor(){
+ @Override
+ public void execute(Runnable command) {
+ mHandler.post(command);
+ }
+ };
+ mRequestCallback = new ClientAutofillRequestCallback(mHandler, (id)-> mMap.get(id));
+ getAutofillManager().setAutofillRequestCallback(executor, mRequestCallback);
+ }
+
+ @Override
+ public void addChild(View child) {
+ throw new AssertionError("Uses addChild(View, String) instead");
+ }
+
+ public void addChild(View child, String id) {
+ Log.d(TAG, "addChild(" + child + "): id=" + child.getAutofillId());
+ super.addChild(child);
+
+ mMap.put(id, child.getAutofillId());
+ }
+
+ public ClientAutofillRequestCallback.Replier getReplier() {
+ return mRequestCallback.getReplier();
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/ManualAuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/ManualAuthenticationActivity.java
index 58b67d7..e3ebd1f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/ManualAuthenticationActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/ManualAuthenticationActivity.java
@@ -58,7 +58,7 @@
result = sResponse.asFillResponse(/* contexts= */ null,
(id) -> Helper.findNodeByResourceId(structure, id));
} else if (sDataset != null) {
- result = sDataset.asDataset(
+ result = sDataset.asDatasetWithNodeResolver(
(id) -> Helper.findNodeByResourceId(structure, id));
} else {
throw new IllegalStateException("no dataset or response");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/OptionalSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/OptionalSaveActivity.java
index 22587d2..2d0fdf9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/OptionalSaveActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/OptionalSaveActivity.java
@@ -105,11 +105,7 @@
finish();
}
- /**
- * Sets the expectation for an auto-fill request, so it can be asserted through
- * {@link #assertAutoFilled()} later.
- */
- public void expectAutoFill(@Nullable String address1, @Nullable String address2,
+ public void expectTextChange(@Nullable String address1, @Nullable String address2,
@Nullable String city,
@Nullable String favColor) {
mExpectation = new FillExpectation(address1, address2, city, favColor);
@@ -128,11 +124,16 @@
}
/**
- * Asserts the activity was auto-filled with the values passed to
- * {@link #expectAutoFill(String, String, String, String)}.
+ * Sets the expectation for an auto-fill request, so it can be asserted through
+ * {@link #assertAutoFilled()} later.
*/
- public void assertAutoFilled() throws Exception {
- assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+ public void expectAutoFill(@Nullable String address1, @Nullable String address2,
+ @Nullable String city,
+ @Nullable String favColor) {
+ expectTextChange(address1, address2, city, favColor);
+ }
+
+ public void assertTextChange() throws Exception {
if (mExpectation.address1Watcher != null) {
mExpectation.address1Watcher.assertAutoFilled();
}
@@ -148,6 +149,15 @@
}
/**
+ * Asserts the activity was auto-filled with the values passed to
+ * {@link #expectAutoFill(String, String, String, String)}.
+ */
+ public void assertAutoFilled() throws Exception {
+ assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+ assertTextChange();
+ }
+
+ /**
* Holder for the expected auto-fill values.
*/
private final class FillExpectation {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/PreSimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/PreSimpleSaveActivity.java
index 02881cb..ab0b619 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/PreSimpleSaveActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/PreSimpleSaveActivity.java
@@ -61,12 +61,25 @@
});
}
- public FillExpectation expectAutoFill(String input) {
+ /**
+ * Set the EditText input or password value and wait until text change.
+ */
+ public void setTextAndWaitTextChange(String input) throws Exception {
+ final FillExpectation expectation = expectInputTextChange(input);
+ syncRunOnUiThread(() -> mPreInput.setText(input));
+ expectation.assertTextChange();
+ }
+
+ public FillExpectation expectInputTextChange(String input) {
final FillExpectation expectation = new FillExpectation(input);
mPreInput.addTextChangedListener(expectation.mInputWatcher);
return expectation;
}
+ public FillExpectation expectAutoFill(String input) {
+ return expectInputTextChange(input);
+ }
+
public EditText getPreInput() {
return mPreInput;
}
@@ -78,6 +91,10 @@
mInputWatcher = new OneTimeTextWatcher("input", mPreInput, input);
}
+ public void assertTextChange() throws Exception {
+ mInputWatcher.assertAutoFilled();
+ }
+
public void assertAutoFilled() throws Exception {
mInputWatcher.assertAutoFilled();
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleSaveActivity.java
index 2866cbc..6f8f8d6 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleSaveActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleSaveActivity.java
@@ -105,21 +105,41 @@
mClearFieldsOnSubmit = flag;
}
- public FillExpectation expectInputTextChange(String input) {
+ /**
+ * Set the EditText input or password value and wait until text change.
+ */
+ public void setTextAndWaitTextChange(String input, String password) throws Exception {
+ FillExpectation changeExpectation = expectInputPasswordTextChange(input, password);
+ syncRunOnUiThread(() -> {
+ if (input != null) {
+ mInput.setText(input);
+ }
+ if (password != null) {
+ mPassword.setText(password);
+ }
+ });
+ changeExpectation.assertTextChange();
+ }
+
+ public FillExpectation expectAutoFill(String input) {
final FillExpectation expectation = new FillExpectation(input, null);
mInput.addTextChangedListener(expectation.mInputWatcher);
return expectation;
}
- public FillExpectation expectAutoFill(String input) {
- return expectInputTextChange(input);
+ public FillExpectation expectInputPasswordTextChange(String input, String password) {
+ final FillExpectation expectation = new FillExpectation(input, password);
+ if (expectation.mInputWatcher != null) {
+ mInput.addTextChangedListener(expectation.mInputWatcher);
+ }
+ if (expectation.mPasswordWatcher != null) {
+ mPassword.addTextChangedListener(expectation.mPasswordWatcher);
+ }
+ return expectation;
}
public FillExpectation expectAutoFill(String input, String password) {
- final FillExpectation expectation = new FillExpectation(input, password);
- mInput.addTextChangedListener(expectation.mInputWatcher);
- mPassword.addTextChangedListener(expectation.mPasswordWatcher);
- return expectation;
+ return expectInputPasswordTextChange(input, password);
}
public EditText getInput() {
@@ -131,7 +151,9 @@
private final OneTimeTextWatcher mPasswordWatcher;
private FillExpectation(String input, String password) {
- mInputWatcher = new OneTimeTextWatcher("input", mInput, input);
+ mInputWatcher = input == null
+ ? null
+ : new OneTimeTextWatcher("input", mInput, input);
mPasswordWatcher = password == null
? null
: new OneTimeTextWatcher("password", mPassword, password);
@@ -142,7 +164,9 @@
}
public void assertAutoFilled() throws Exception {
- mInputWatcher.assertAutoFilled();
+ if (mInputWatcher != null) {
+ mInputWatcher.assertAutoFilled();
+ }
if (mPasswordWatcher != null) {
mPasswordWatcher.assertAutoFilled();
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/client/ClientSuggestionsInlineTest.java b/tests/autofillservice/src/android/autofillservice/cts/client/ClientSuggestionsInlineTest.java
new file mode 100644
index 0000000..1419704
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/client/ClientSuggestionsInlineTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.client;
+
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.autofillservice.cts.commontests.ClientSuggestionsCommonTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.ClientAutofillRequestCallback;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.os.Bundle;
+
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.Test;
+
+/**
+ * Tests client suggestions behaviors for the inline mode.
+ */
+public class ClientSuggestionsInlineTest extends ClientSuggestionsCommonTestCase {
+
+ public ClientSuggestionsInlineTest() {
+ super(getInlineUiBot());
+ }
+
+ @Override
+ protected boolean isInlineMode() {
+ return true;
+ }
+
+ @Test
+ public void testImeDisableClientSuggestions_showDropdownUi() throws Exception {
+ // Set service.
+ enableService();
+ final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
+ assumeTrue("MockIME not available", mockImeSession != null);
+
+ // Disable inline suggestions for the client.
+ final Bundle bundle = new Bundle();
+ bundle.putBoolean("ClientSuggestions", false);
+ mockImeSession.callSetInlineSuggestionsExtras(bundle);
+
+ // Set expectations.
+ mClientReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation("The Dude", isInlineMode())
+ .build());
+
+ // Trigger auto-fill.
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mUiBot.waitForIdleSync();
+
+ // Check that no inline requests are sent to the client.
+ final ClientAutofillRequestCallback.FillRequest clientRequest =
+ mClientReplier.getNextFillRequest();
+ assertThat(clientRequest.inlineRequest).isNull();
+
+ // Check dropdown UI shown.
+ getDropdownUiBot().assertDatasets("The Dude");
+ }
+
+ @Test
+ public void testImeDisableClientSuggestions_fallbackThenShowInline() throws Exception {
+ // Set service.
+ enableService();
+
+ final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
+ assumeTrue("MockIME not available", mockImeSession != null);
+
+ // Disable inline suggestions for the client.
+ final Bundle bundle = new Bundle();
+ bundle.putBoolean("ClientSuggestions", false);
+ mockImeSession.callSetInlineSuggestionsExtras(bundle);
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation("The Dude", isInlineMode())
+ .build());
+
+ mClientReplier.addResponse(NO_RESPONSE);
+
+ // Trigger autofill.
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mUiBot.waitForIdle();
+
+ // Check that no inline requests are sent to the client.
+ final ClientAutofillRequestCallback.FillRequest clientRequest =
+ mClientReplier.getNextFillRequest();
+ assertThat(clientRequest.inlineRequest).isNull();
+
+ // Check that the inline request is sent to the service.
+ final InstrumentedAutoFillService.FillRequest fillRequest = sReplier.getNextFillRequest();
+ assertThat(fillRequest.inlineRequest).isNotNull();
+
+ mActivity.expectAutoFill("dude", "sweet");
+
+ // Select the dataset.
+ mUiBot.selectDataset("The Dude");
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+
+ @Test
+ public void testImeDisableServiceSuggestions_fallbackThenShowDropdownUi() throws Exception {
+ // Set service.
+ enableService();
+
+ final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
+ assumeTrue("MockIME not available", mockImeSession != null);
+
+ // Disable inline suggestions for the default service.
+ final Bundle bundle = new Bundle();
+ bundle.putBoolean("ServiceSuggestions", false);
+ mockImeSession.callSetInlineSuggestionsExtras(bundle);
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation("The Dude", isInlineMode())
+ .build());
+
+ mClientReplier.addResponse(NO_RESPONSE);
+
+ // Trigger autofill.
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mUiBot.waitForIdle();
+
+ // Check that the inline request is sent to the client.
+ final ClientAutofillRequestCallback.FillRequest clientRequest =
+ mClientReplier.getNextFillRequest();
+ assertThat(clientRequest.inlineRequest).isNotNull();
+
+ // Check that no inline requests are sent to the service.
+ final InstrumentedAutoFillService.FillRequest fillRequest = sReplier.getNextFillRequest();
+ assertThat(fillRequest.inlineRequest).isNull();
+
+ mActivity.expectAutoFill("dude", "sweet");
+
+ // Select the dataset on the dropdown UI.
+ getDropdownUiBot().selectDataset("The Dude");
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/client/ClientSuggestionsTest.java b/tests/autofillservice/src/android/autofillservice/cts/client/ClientSuggestionsTest.java
new file mode 100644
index 0000000..895ec3a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/client/ClientSuggestionsTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.client;
+
+import android.autofillservice.cts.commontests.ClientSuggestionsCommonTestCase;
+
+/**
+ * Tests client suggestions behaviors for the dropdown mode.
+ */
+public class ClientSuggestionsTest extends ClientSuggestionsCommonTestCase {
+
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/ClientSuggestionsCommonTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/ClientSuggestionsCommonTestCase.java
new file mode 100644
index 0000000..e39067f
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/ClientSuggestionsCommonTestCase.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.commontests;
+
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_EMPTY;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.ClientSuggestionsActivity;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.ClientAutofillRequestCallback;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.autofillservice.cts.testcore.UiBot;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.widget.EditText;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+
+/**
+ * This is the test case covering most scenarios - other test cases will cover characteristics
+ * specific to that test's activity (for example, custom views).
+ */
+public abstract class ClientSuggestionsCommonTestCase
+ extends AutoFillServiceTestCase.AutoActivityLaunch<ClientSuggestionsActivity> {
+
+ private static final String TAG = "ClientSuggestions";
+ protected ClientSuggestionsActivity mActivity;
+ protected ClientAutofillRequestCallback.Replier mClientReplier;
+
+ protected ClientSuggestionsCommonTestCase() {}
+
+ protected ClientSuggestionsCommonTestCase(UiBot inlineUiBot) {
+ super(inlineUiBot);
+ }
+
+ @Test
+ public void testAutoFillOneDataset() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ mClientReplier.addResponse(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation("The Dude", isInlineMode())
+ .build());
+
+ // Trigger autofill.
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ sReplier.assertOnFillRequestNotCalled();
+ mClientReplier.assertReceivedRequest();
+
+ mActivity.expectAutoFill("dude", "sweet");
+
+ // Select the dataset.
+ mUiBot.selectDataset("The Dude");
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+
+ @Test
+ public void testAutoFillNoDatasets_fallbackDefaultService() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation("The Dude", isInlineMode())
+ .build());
+
+ mClientReplier.addResponse(NO_RESPONSE);
+
+ // Trigger autofill.
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mUiBot.waitForIdle();
+ sReplier.getNextFillRequest();
+ mClientReplier.assertReceivedRequest();
+
+ mActivity.expectAutoFill("dude", "sweet");
+
+ // Select the dataset.
+ mUiBot.selectDataset("The Dude");
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+
+ @Test
+ @AppModeFull(reason = "testAutoFillNoDatasets_fallbackDefaultService() is enough")
+ public void testManualRequestAfterFallbackDefaultService() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation("The Dude", isInlineMode())
+ .build());
+
+ mClientReplier.addResponse(NO_RESPONSE);
+
+ // Trigger autofill.
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mUiBot.waitForIdle();
+ sReplier.getNextFillRequest();
+ mClientReplier.assertReceivedRequest();
+
+ // The dataset shown.
+ mUiBot.assertDatasets("The Dude");
+
+ // Set expectations.
+ sReplier.addResponse(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "DUDE")
+ .setField(ID_PASSWORD, "SWEET")
+ .setPresentation("THE DUDE", isInlineMode())
+ .build());
+
+ // Trigger autofill.
+ mUiBot.waitForWindowChange(() -> mActivity.forceAutofillOnUsername());
+ sReplier.getNextFillRequest();
+ mClientReplier.assertNoUnhandledFillRequests();
+
+ mActivity.expectAutoFill("DUDE", "SWEET");
+
+ // Select the dataset.
+ mUiBot.selectDataset("THE DUDE");
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+
+ @Test
+ @AppModeFull(reason = "testAutoFillNoDatasets_fallbackDefaultService() is enough")
+ public void testNewFieldAddedAfterFallbackDefaultService() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation("The Dude", isInlineMode())
+ .build());
+
+ mClientReplier.addResponse(NO_RESPONSE);
+
+ // Trigger autofill.
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mUiBot.waitForIdle();
+ sReplier.getNextFillRequest();
+ mClientReplier.assertReceivedRequest();
+
+ // The dataset shown.
+ mUiBot.assertDatasets("The Dude");
+
+ // Try again, in a field that was added after the first request
+ final EditText child = new EditText(mActivity);
+ child.setId(R.id.empty);
+ mActivity.addChild(child, ID_EMPTY);
+ final OneTimeTextWatcher watcher = new OneTimeTextWatcher("child", child,
+ "new view on the block");
+ child.addTextChangedListener(watcher);
+ sReplier.addResponse(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setField(ID_EMPTY, "new view on the block")
+ .setPresentation("The Dude", isInlineMode())
+ .build());
+
+ mActivity.syncRunOnUiThread(() -> child.requestFocus());
+ mUiBot.waitForIdle();
+ sReplier.getNextFillRequest();
+ mClientReplier.assertNoUnhandledFillRequests();
+
+ mActivity.expectAutoFill("dude", "sweet");
+
+ // Select the dataset.
+ mUiBot.selectDataset("The Dude");
+ mUiBot.waitForIdle();
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ watcher.assertAutoFilled();
+ }
+
+ @Test
+ public void testNoDatasetsAfterFallbackDefaultService() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(NO_RESPONSE);
+ mClientReplier.addResponse(NO_RESPONSE);
+
+ // Trigger autofill.
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mUiBot.waitForIdle();
+
+ mClientReplier.assertReceivedRequest();
+ sReplier.getNextFillRequest();
+
+ // Make sure UI is not shown.
+ mUiBot.assertNoDatasetsEver();
+ }
+
+ @Test
+ @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+ public void testAutoFillNoDatasets() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ setEmptyClientResponse();
+
+ // Trigger autofill.
+ mUiBot.selectByRelativeId(ID_USERNAME);
+
+ mClientReplier.assertReceivedRequest();
+
+ // Make sure UI is not shown.
+ mUiBot.assertNoDatasetsEver();
+ }
+
+ @Test
+ @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+ public void testNewFieldAddedAfterFirstRequest() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ setEmptyClientResponse();
+
+ // Trigger autofill.
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mClientReplier.assertReceivedRequest();
+
+ // Make sure UI is not shown.
+ mUiBot.assertNoDatasetsEver();
+
+ // Try again, in a field that was added after the first request
+ final EditText child = new EditText(mActivity);
+ child.setId(R.id.empty);
+ mActivity.addChild(child, ID_EMPTY);
+ final OneTimeTextWatcher watcher = new OneTimeTextWatcher("child", child,
+ "new view on the block");
+ child.addTextChangedListener(watcher);
+ mClientReplier.addResponse(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setField(ID_EMPTY, "new view on the block")
+ .setPresentation("The Dude", isInlineMode())
+ .build());
+
+ mActivity.syncRunOnUiThread(() -> child.requestFocus());
+
+ mClientReplier.assertReceivedRequest();
+ mActivity.expectAutoFill("dude", "sweet");
+
+ // Select the dataset.
+ mUiBot.selectDataset("The Dude");
+
+ // Check the results.
+ // Check username and password fields
+ mActivity.assertAutoFilled();
+ // Check the new added field
+ watcher.assertAutoFilled();
+ }
+
+ @NonNull
+ @Override
+ protected AutofillActivityTestRule<ClientSuggestionsActivity> getActivityRule() {
+ return new AutofillActivityTestRule<ClientSuggestionsActivity>(
+ ClientSuggestionsActivity.class) {
+ @Override
+ protected void afterActivityLaunched() {
+ mActivity = getActivity();
+ mClientReplier = mActivity.getReplier();
+ }
+ };
+ }
+
+ private void setEmptyClientResponse() {
+ mClientReplier.addResponse(new CannedFillResponse.Builder()
+ .setExtras(new Bundle())
+ .build());
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java
index 1880ed3..34e1eb1 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java
@@ -2004,7 +2004,7 @@
mContext.unregisterReceiver(this);
latch.countDown();
}
- }, intentFilter);
+ }, intentFilter, Context.RECEIVER_NOT_EXPORTED);
// Trigger the negative button.
mUiBot.saveForAutofill(style, /* yesDoIt= */ false, SAVE_DATA_TYPE_PASSWORD);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityCompatModeTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityCompatModeTest.java
index 66ab8aa..3dc464d 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityCompatModeTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityCompatModeTest.java
@@ -26,10 +26,8 @@
import static android.autofillservice.cts.testcore.Helper.getContext;
import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceCompatMode.SERVICE_NAME;
import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceCompatMode.SERVICE_PACKAGE;
-import static android.provider.Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import static com.android.compatibility.common.util.SettingsUtils.NAMESPACE_GLOBAL;
+import static android.view.autofill.AutofillManager.DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES;
import static com.google.common.truth.Truth.assertThat;
@@ -45,13 +43,13 @@
import android.os.SystemClock;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
import android.service.autofill.SaveInfo;
-import com.android.compatibility.common.util.SettingsStateChangerRule;
-import com.android.compatibility.common.util.SettingsUtils;
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
import org.junit.After;
-import org.junit.ClassRule;
+import org.junit.Before;
import org.junit.Test;
/**
@@ -60,18 +58,23 @@
*/
public class VirtualContainerActivityCompatModeTest extends VirtualContainerActivityTest {
- @ClassRule
- public static final SettingsStateChangerRule sCompatModeChanger = new SettingsStateChangerRule(
- sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
- SERVICE_PACKAGE + "[my_url_bar]");
+ private final DeviceConfigStateHelper mAutofillDeviceConfig =
+ new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_AUTOFILL);
public VirtualContainerActivityCompatModeTest() {
super(true);
}
+ @Before
+ public void setCompatMode() {
+ mAutofillDeviceConfig.set(DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
+ SERVICE_PACKAGE + "[my_url_bar]");
+ }
+
@After
public void resetCompatMode() {
sContext.getApplicationContext().setAutofillOptions(null);
+ mAutofillDeviceConfig.restoreOriginalValues();
}
@Override
@@ -106,7 +109,7 @@
@Presubmit
@Test
public void testMultipleUrlBars_firstDoesNotExist() throws Exception {
- SettingsUtils.syncSet(sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
+ mAutofillDeviceConfig.set(DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
SERVICE_PACKAGE + "[first_am_i,my_url_bar]");
// Set service.
@@ -131,7 +134,7 @@
@Test
@AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
public void testMultipleUrlBars_bothExist() throws Exception {
- SettingsUtils.syncSet(sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
+ mAutofillDeviceConfig.set(DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
SERVICE_PACKAGE + "[my_url_bar,my_url_bar2]");
// Set service.
@@ -286,7 +289,7 @@
// Fill in some stuff
mActivity.mUsername.setText("foo");
sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
- SystemClock.sleep(300);
+ SystemClock.sleep(Timeouts.FILL_TIMEOUT.ms());
focusToPasswordExpectNoWindowEvent();
sReplier.getNextFillRequest();
mActivity.mPassword.setText("bar");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
index 1ce3a8f..bd416b9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
@@ -40,6 +40,7 @@
import android.autofillservice.cts.testcore.UiBot;
import android.content.IntentSender;
import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.FlakyTest;
import android.platform.test.annotations.Presubmit;
import android.view.inputmethod.InlineSuggestionsRequest;
@@ -177,6 +178,7 @@
}
@Test
+ @FlakyTest(bugId = 185876679)
@AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
public void testDatasetAuthTwoFieldsUserCancelsFirstAttempt() throws Exception {
datasetAuthTwoFields(/* cancelFirstAttempt */ true);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
index f1e039d..7691b6b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
@@ -374,6 +374,72 @@
}
@Test
+ public void testImeDisableServiceSuggestions_fallbackDropdownUi() throws Exception {
+ // Set service.
+ enableService();
+
+ final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
+ assumeTrue("MockIME not available", mockImeSession != null);
+
+ // Disable inline suggestions for the default service.
+ final Bundle bundle = new Bundle();
+ bundle.putBoolean("ServiceSuggestions", false);
+ mockImeSession.callSetInlineSuggestionsExtras(bundle);
+
+ final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+ .addDataset(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setPresentation(createPresentation("The Username"))
+ .setInlinePresentation(createInlinePresentation("The Username"))
+ .build());
+ sReplier.addResponse(builder.build());
+
+ // Trigger auto-fill.
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mUiBot.waitForIdleSync();
+
+ // Check that no inline requests are sent to the service.
+ final InstrumentedAutoFillService.FillRequest request = sReplier.getNextFillRequest();
+ assertThat(request.inlineRequest).isNull();
+
+ // Check dropdown UI shown.
+ getDropdownUiBot().assertDatasets("The Username");
+ }
+
+ @Test
+ public void testImeDisableInlineSuggestions_fallbackDropdownUi() throws Exception {
+ // Set service.
+ enableService();
+
+ final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
+ assumeTrue("MockIME not available", mockImeSession != null);
+
+ // Disable inline suggestions for the default service.
+ final Bundle bundle = new Bundle();
+ bundle.putBoolean("InlineSuggestions", false);
+ mockImeSession.callSetInlineSuggestionsExtras(bundle);
+
+ final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+ .addDataset(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setPresentation(createPresentation("The Username"))
+ .setInlinePresentation(createInlinePresentation("The Username"))
+ .build());
+ sReplier.addResponse(builder.build());
+
+ // Trigger auto-fill.
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mUiBot.waitForIdleSync();
+
+ // Check that no inline requests are sent to the service.
+ final InstrumentedAutoFillService.FillRequest request = sReplier.getNextFillRequest();
+ assertThat(request.inlineRequest).isNull();
+
+ // Check dropdown UI shown.
+ getDropdownUiBot().assertDatasets("The Username");
+ }
+
+ @Test
public void testScrollSuggestionView() throws Exception {
// Set service.
enableService();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
index d07f95b..75c28e2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
@@ -92,10 +92,8 @@
mUiBot.assertNoDatasetsEver();
// Change input
- final SimpleSaveActivity.FillExpectation changeExpectation =
- mActivity.expectInputTextChange("ID");
- mActivity.syncRunOnUiThread(() -> mActivity.getInput().setText("ID"));
- changeExpectation.assertTextChange();
+ mActivity.setTextAndWaitTextChange(/* input= */ "ID", /* password= */ null);
+
// Trigger save UI.
mUiBot.selectByRelativeId(ID_COMMIT);
@@ -147,10 +145,7 @@
fillExpectation.assertAutoFilled();
// Change input
- final SimpleSaveActivity.FillExpectation changeExpectation =
- mActivity.expectInputTextChange("ID");
- mActivity.syncRunOnUiThread(() -> mActivity.getInput().setText("ID"));
- changeExpectation.assertTextChange();
+ mActivity.setTextAndWaitTextChange(/* input= */ "ID", /* password= */ null);
// Trigger save UI.
mUiBot.selectByRelativeId(ID_COMMIT);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/OnClickActionTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/OnClickActionTest.java
index 8282fec..2d764bc 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/saveui/OnClickActionTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/OnClickActionTest.java
@@ -117,9 +117,8 @@
sReplier.getNextFillRequest();
// Trigger save.
+ mActivity.setTextAndWaitTextChange(/* input= */ "42", /* password= */ "108");
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("42");
- mActivity.mPassword.setText("108");
mActivity.mCommit.performClick();
});
final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/OptionalSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/OptionalSaveActivityTest.java
index 1e38ae4..3231080 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/saveui/OptionalSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/OptionalSaveActivityTest.java
@@ -752,10 +752,13 @@
mActivity.assertAutoFilled();
// Change required and optional field.
+ mActivity.expectTextChange(/* address1= */ null, /* address2= */
+ "Simpsons House", /* city= */ "Shelbyville", /* favColor= */ null);
mActivity.syncRunOnUiThread(() -> {
mActivity.mAddress2.setText("Simpsons House");
mActivity.mCity.setText("Shelbyville");
});
+ mActivity.assertTextChange();
// Trigger save...
mActivity.save();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/PreSimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/PreSimpleSaveActivityTest.java
index f7a9030..0ee330c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/saveui/PreSimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/PreSimpleSaveActivityTest.java
@@ -69,7 +69,7 @@
@Override
protected void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
throws Exception {
- startActivity(false);
+ startActivity(/* remainOnRecents= */ false);
// Set service.
enableService();
@@ -85,10 +85,9 @@
sReplier.getNextFillRequest();
// Trigger save.
- mActivity.syncRunOnUiThread(() -> {
- mActivity.mPreInput.setText("108");
- mActivity.mSubmit.performClick();
- });
+ mActivity.setTextAndWaitTextChange("108");
+ mActivity.syncRunOnUiThread(() -> mActivity.mSubmit.performClick());
+
// Make sure post-save activity is shown...
mUiBot.assertShownByRelativeId(ID_INPUT);
@@ -128,7 +127,7 @@
@Override
protected void tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction action,
boolean manualRequest) throws Exception {
- startActivity(false);
+ startActivity(/* remainOnRecents= */ false);
// Set service.
enableService();
@@ -144,10 +143,9 @@
sReplier.getNextFillRequest();
// Trigger save.
- mActivity.syncRunOnUiThread(() -> {
- mActivity.mPreInput.setText("108");
- mActivity.mSubmit.performClick();
- });
+ mActivity.setTextAndWaitTextChange("108");
+ mActivity.syncRunOnUiThread(() -> mActivity.mSubmit.performClick());
+
// Make sure post-save activity is shown...
mUiBot.assertShownByRelativeId(ID_INPUT);
@@ -203,10 +201,9 @@
sReplier.getNextFillRequest();
// Trigger save.
- newActivty.syncRunOnUiThread(() -> {
- newActivty.mInput.setText("42");
- newActivty.mCommit.performClick();
- });
+ newActivty.setTextAndWaitTextChange(/* input= */ "42", /* password= */ null);
+ newActivty.syncRunOnUiThread(() -> newActivty.mCommit.performClick());
+
// Make sure post-save activity is shown...
mUiBot.assertShownByRelativeId(ID_INPUT);
@@ -221,7 +218,7 @@
@Override
protected void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
throws Exception {
- startActivity(false);
+ startActivity(/* remainOnRecents= */ false);
// Set service.
enableService();
@@ -237,10 +234,9 @@
sReplier.getNextFillRequest();
// Trigger save.
- mActivity.syncRunOnUiThread(() -> {
- mActivity.mPreInput.setText("108");
- mActivity.mSubmit.performClick();
- });
+ mActivity.setTextAndWaitTextChange("108");
+ mActivity.syncRunOnUiThread(() -> mActivity.mSubmit.performClick());
+
// Make sure post-save activity is shown...
mUiBot.assertShownByRelativeId(ID_INPUT);
@@ -276,7 +272,7 @@
protected void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
throws Exception {
// Prepare activity.
- startActivity(false);
+ startActivity(/* remainOnRecents= */ false);
mActivity.mPreInput.getRootView()
.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
@@ -295,10 +291,9 @@
sReplier.getNextFillRequest();
// Trigger save.
- mActivity.syncRunOnUiThread(() -> {
- mActivity.mPreInput.setText("108");
- mActivity.mSubmit.performClick();
- });
+ mActivity.setTextAndWaitTextChange("108");
+ mActivity.syncRunOnUiThread(() -> mActivity.mSubmit.performClick());
+
final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
// Tap the link.
@@ -329,10 +324,8 @@
sReplier.getNextFillRequest();
// Trigger save.
- newActivty.syncRunOnUiThread(() -> {
- newActivty.mInput.setText("42");
- newActivty.mCommit.performClick();
- });
+ newActivty.setTextAndWaitTextChange(/* input= */ "42", /* password= */ null);
+ newActivty.syncRunOnUiThread(() -> newActivty.mCommit.performClick());
// Make sure post-save activity is shown...
mUiBot.assertShownByRelativeId(ID_INPUT);
@@ -346,7 +339,7 @@
@Override
protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception {
- startActivity(false);
+ startActivity(/* remainOnRecents= */ false);
// Set service.
enableService();
@@ -375,10 +368,9 @@
sReplier.getNextFillRequest();
// Trigger save.
- mActivity.syncRunOnUiThread(() -> {
- mActivity.mPreInput.setText("108");
- mActivity.mSubmit.performClick();
- });
+ mActivity.setTextAndWaitTextChange("108");
+ mActivity.syncRunOnUiThread(() -> mActivity.mSubmit.performClick());
+
// Make sure post-save activity is shown...
mUiBot.assertShownByRelativeId(ID_INPUT);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/SimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/SimpleSaveActivityTest.java
index 65d85bf..b06a187 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/saveui/SimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/SimpleSaveActivityTest.java
@@ -142,13 +142,12 @@
sReplier.getNextFillRequest();
// Select dataset.
- final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
+ final FillExpectation autofillExpectation = mActivity.expectAutoFill("id", "pass");
mUiBot.selectDataset("YO");
- autofillExpecation.assertAutoFilled();
+ autofillExpectation.assertAutoFilled();
+ mActivity.setTextAndWaitTextChange(/* input= */ "ID", /* password= */ "PASS");
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("ID");
- mActivity.mPassword.setText("PASS");
mActivity.mCommit.performClick();
});
final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
@@ -195,13 +194,12 @@
Helper.assertEqualsToLargeString(hintsOnFill[2]);
// Select dataset.
- final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
+ final FillExpectation autofillExpectation = mActivity.expectAutoFill("id", "pass");
mUiBot.selectDataset("YO");
- autofillExpecation.assertAutoFilled();
+ autofillExpectation.assertAutoFilled();
+ mActivity.setTextAndWaitTextChange(/* input= */ "ID", /* password= */ "PASS");
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("ID");
- mActivity.mPassword.setText("PASS");
mActivity.mCommit.performClick();
});
final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
@@ -264,8 +262,11 @@
assertWithMessage("wrong value for 'password'").that(visiblePassword).hasLength(4);
// Trigger save...
+ final SimpleSaveActivity.FillExpectation changeExpectation =
+ mActivity.expectInputPasswordTextChange("ID", "PASS");
input.setText("ID");
password.setText("PASS");
+ changeExpectation.assertTextChange();
mUiBot.assertShownByRelativeId(ID_COMMIT).click();
mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
@@ -314,10 +315,9 @@
sReplier.getNextFillRequest();
// Trigger save.
- mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("108");
- mActivity.mCommit.performClick();
- });
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
+
+ mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick());
UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
if (rotate) {
@@ -359,7 +359,7 @@
sReplier.getNextFillRequest();
// Set 1st field but don't commit session
- mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
mUiBot.assertSaveNotShowing();
// 2nd request
@@ -375,10 +375,9 @@
sReplier.getNextFillRequest();
// Trigger save.
- mActivity.syncRunOnUiThread(() -> {
- mActivity.mPassword.setText("42");
- mActivity.mCommit.performClick();
- });
+ mActivity.setTextAndWaitTextChange(/* input= */ null, /* password= */ "42");
+
+ mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick());
final UiObject2 saveUi = mUiBot.assertSaveShowing(null, SAVE_DATA_TYPE_USERNAME,
SAVE_DATA_TYPE_PASSWORD);
@@ -415,10 +414,8 @@
sReplier.getNextFillRequest();
// Trigger delayed save.
- mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("108");
- mActivity.mCommit.performClick();
- });
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
+ mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick());
mUiBot.assertSaveNotShowing();
// 2nd fragment.
@@ -444,10 +441,8 @@
sReplier.getNextFillRequest();
// Trigger delayed save.
- mActivity.syncRunOnUiThread(() -> {
- mActivity.mPassword.setText("42");
- mActivity.mCommit.performClick();
- });
+ mActivity.setTextAndWaitTextChange(/* input= */ null, /* password= */ "42");
+ mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick());
// Save it...
mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
@@ -485,8 +480,8 @@
sReplier.getNextFillRequest();
// Trigger save.
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("108");
mActivity.mCommit.performClick();
// Disable autofill so it's not triggered again after WelcomeActivity finishes
@@ -520,8 +515,8 @@
sReplier.getNextFillRequest();
// Trigger save.
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("108");
mActivity.mCommit.performClick();
});
@@ -581,8 +576,8 @@
sReplier.getNextFillRequest();
// Trigger save.
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("108");
mActivity.mCommit.performClick();
});
@@ -604,8 +599,8 @@
.setPresentation(createPresentation("YO"))
.build())
.build());
+ mActivity.setTextAndWaitTextChange(/* input= */ "", /* password= */ null);
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("");
mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
});
sReplier.getNextFillRequest();
@@ -637,8 +632,8 @@
sReplier.getNextFillRequest();
// Trigger save.
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("108");
mActivity.mCommit.performClick();
});
UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
@@ -691,8 +686,8 @@
mActivity.getAutofillManager().cancel();
// Trigger save.
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("108");
mActivity.mCommit.performClick();
});
@@ -741,8 +736,8 @@
sReplier.getNextFillRequest();
// Trigger save.
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("108");
mActivity.mCommit.performClick();
});
mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
@@ -834,8 +829,8 @@
mUiBot.assertNoDatasetsEver();
// Trigger save, but don't tap it.
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("108");
mActivity.mCommit.performClick();
});
mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
@@ -890,8 +885,8 @@
sReplier.getNextFillRequest();
// Trigger save.
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("108");
mActivity.mCommit.performClick();
});
final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
@@ -958,8 +953,8 @@
sReplier.getNextFillRequest();
// Trigger save.
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("108");
mActivity.mCommit.performClick();
});
final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
@@ -1013,8 +1008,8 @@
sReplier.getNextFillRequest();
// Trigger save.
+ mActivity.setTextAndWaitTextChange(/* input= */ "42", /* password= */ null);
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("42");
mActivity.mCommit.performClick();
});
@@ -1045,8 +1040,8 @@
sReplier.getNextFillRequest();
// Trigger save.
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("108");
mActivity.mCommit.performClick();
});
final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
@@ -1120,9 +1115,8 @@
mUiBot.selectDataset(picker2, "D2");
autofillExpecation2.assertAutoFilled();
+ mActivity.setTextAndWaitTextChange(/* input= */ "ID", /* password= */ "PASS");
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("ID");
- mActivity.mPassword.setText("PASS");
mActivity.mCommit.performClick();
});
final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
@@ -1163,8 +1157,8 @@
sReplier.getNextFillRequest();
// Trigger save.
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("108");
mActivity.mCommit.performClick();
});
final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
@@ -1194,8 +1188,8 @@
() -> mActivity.getAutofillManager().requestAutofill(mActivity.mPassword));
sReplier.getNextFillRequest();
+ mActivity.setTextAndWaitTextChange(/* input= */ null, /* password= */ "42");
mActivity.syncRunOnUiThread(() -> {
- mActivity.mPassword.setText("42");
mActivity.mCommit.performClick();
});
mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
@@ -1233,6 +1227,7 @@
sReplier.getNextFillRequest();
// Trigger save.
+ // TODO: handle wait text change for ntiTrimmerTextWatcher
mActivity.syncRunOnUiThread(() -> {
mActivity.mInput.setText("id");
mActivity.mPassword.setText("pass");
@@ -1275,9 +1270,8 @@
mUiBot.assertNoDatasetsEver();
// Trigger save.
+ mActivity.setTextAndWaitTextChange(/* input= */ "#id#", /* password= */ "#pass#");
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("#id#");
- mActivity.mPassword.setText("#pass#");
mActivity.mCommit.performClick();
});
@@ -1323,6 +1317,7 @@
mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
sReplier.getNextFillRequest();
+ // TODO: handle wait text change for ntiTrimmerTextWatcher
mActivity.syncRunOnUiThread(() -> {
mActivity.mInput.setText("id");
mActivity.mPassword.setText("pass");
@@ -1361,9 +1356,8 @@
mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
sReplier.getNextFillRequest();
+ mActivity.setTextAndWaitTextChange(/* input= */ "id", /* password= */ "#pass#");
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("id");
- mActivity.mPassword.setText("#pass#");
mActivity.mCommit.performClick();
});
@@ -1399,9 +1393,8 @@
mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
sReplier.getNextFillRequest();
+ mActivity.setTextAndWaitTextChange(/* input= */ "id", /* password= */ "pass");
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("id");
- mActivity.mPassword.setText("pass");
mActivity.mCommit.performClick();
});
@@ -1439,9 +1432,8 @@
mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
sReplier.getNextFillRequest();
+ mActivity.setTextAndWaitTextChange(/* input= */ "id", /* password= */ "pass");
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("id");
- mActivity.mPassword.setText("pass");
mActivity.mCommit.performClick();
});
@@ -1609,8 +1601,8 @@
if (condition == SetTextCondition.NORMAL) {
sReplier.getNextFillRequest();
+ mActivity.setTextAndWaitTextChange(/* input= */ "100", /* password= */ "pass");
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("100");
mActivity.mCommit.performClick();
});
@@ -1662,7 +1654,7 @@
sReplier.getNextFillRequest();
// Trigger save.
- mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
// Take a screenshot to make sure button doesn't disappear.
final String commitBefore = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
@@ -1724,8 +1716,8 @@
mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
sReplier.getNextFillRequest();
// Trigger save.
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("108");
mActivity.mCommit.performClick();
});
final UiObject2 saveUi;
@@ -1862,8 +1854,8 @@
sReplier.getNextFillRequest();
// Trigger save.
+ mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
mActivity.syncRunOnUiThread(() -> {
- mActivity.mInput.setText("108");
mActivity.mCommit.performClick();
});
// Waits for the commit be processed
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/WebViewMultiScreenLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/WebViewMultiScreenLoginActivityTest.java
index 94a88c4..009a9cb 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/WebViewMultiScreenLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/WebViewMultiScreenLoginActivityTest.java
@@ -24,7 +24,6 @@
import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
import static android.autofillservice.cts.testcore.Helper.findNodeByHtmlName;
-import static android.autofillservice.cts.testcore.Helper.getAutofillId;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
@@ -103,7 +102,7 @@
sReplier.addResponse(new CannedFillResponse.Builder()
.setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, HTML_NAME_USERNAME)
.setSaveInfoDecorator((builder, nodeResolver) -> {
- final AutofillId usernameId = getAutofillId(nodeResolver, HTML_NAME_USERNAME);
+ final AutofillId usernameId = nodeResolver.apply(HTML_NAME_USERNAME);
final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
.Builder(usernameId, MATCH_ALL, "$1").build();
builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
@@ -155,7 +154,7 @@
sReplier.addResponse(new CannedFillResponse.Builder()
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, HTML_NAME_PASSWORD)
.setSaveInfoDecorator((builder, nodeResolver) -> {
- final AutofillId passwordId = getAutofillId(nodeResolver, HTML_NAME_PASSWORD);
+ final AutofillId passwordId = nodeResolver.apply(HTML_NAME_PASSWORD);
final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
.Builder(passwordId, MATCH_ALL, "$1").build();
builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
@@ -219,7 +218,7 @@
.setIgnoreFields(HTML_NAME_USERNAME)
.setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
.setSaveInfoDecorator((builder, nodeResolver) -> {
- usernameId.set(getAutofillId(nodeResolver, HTML_NAME_USERNAME));
+ usernameId.set(nodeResolver.apply(HTML_NAME_USERNAME));
})
.build());
@@ -258,7 +257,7 @@
.setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
HTML_NAME_PASSWORD)
.setSaveInfoDecorator((builder, nodeResolver) -> {
- final AutofillId passwordId = getAutofillId(nodeResolver, HTML_NAME_PASSWORD);
+ final AutofillId passwordId = nodeResolver.apply(HTML_NAME_PASSWORD);
final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
.Builder(usernameId.get(), MATCH_ALL, "$1").build();
final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
index 99ee293..3ec1f12 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
@@ -171,12 +171,28 @@
*/
public FillResponse asFillResponse(@Nullable List<FillContext> contexts,
@NonNull Function<String, ViewNode> nodeResolver) {
+ return asFillResponseWithAutofillId(contexts, (id)-> {
+ ViewNode node = nodeResolver.apply(id);
+ if (node == null) {
+ throw new AssertionError("No node with resource id " + id);
+ }
+ return node.getAutofillId();
+ });
+ }
+
+ /**
+ * Creates a new response, replacing the dataset field ids by the real ids from the assist
+ * structure.
+ */
+ public FillResponse asFillResponseWithAutofillId(@Nullable List<FillContext> contexts,
+ @NonNull Function<String, AutofillId> autofillIdResolver) {
final FillResponse.Builder builder = new FillResponse.Builder()
.setFlags(mFillResponseFlags);
if (mDatasets != null) {
for (CannedDataset cannedDataset : mDatasets) {
- final Dataset dataset = cannedDataset.asDataset(nodeResolver);
- assertWithMessage("Cannot create datase").that(dataset).isNotNull();
+ final Dataset dataset =
+ cannedDataset.asDatasetWithAutofillIdResolver(autofillIdResolver);
+ assertWithMessage("Cannot create dataset").that(dataset).isNotNull();
builder.addDataset(dataset);
}
}
@@ -189,13 +205,14 @@
saveInfoBuilder = mRequiredSavableIds == null || mRequiredSavableIds.length == 0
? new SaveInfo.Builder(mSaveType)
: new SaveInfo.Builder(mSaveType,
- getAutofillIds(nodeResolver, mRequiredSavableIds));
+ getAutofillIds(autofillIdResolver, mRequiredSavableIds));
}
saveInfoBuilder.setFlags(mSaveInfoFlags);
if (mOptionalSavableIds != null) {
- saveInfoBuilder.setOptionalIds(getAutofillIds(nodeResolver, mOptionalSavableIds));
+ saveInfoBuilder.setOptionalIds(
+ getAutofillIds(autofillIdResolver, mOptionalSavableIds));
}
if (mSaveDescription != null) {
saveInfoBuilder.setDescription(mSaveDescription);
@@ -217,7 +234,7 @@
if (saveInfoBuilder != null) {
// TODO: merge decorator and visitor
if (mSaveInfoDecorator != null) {
- mSaveInfoDecorator.decorate(saveInfoBuilder, nodeResolver);
+ mSaveInfoDecorator.decorate(saveInfoBuilder, autofillIdResolver);
}
if (mSaveInfoVisitor != null) {
Log.d(TAG, "Visiting saveInfo " + saveInfoBuilder);
@@ -228,10 +245,10 @@
builder.setSaveInfo(saveInfo);
}
if (mIgnoredIds != null) {
- builder.setIgnoredIds(getAutofillIds(nodeResolver, mIgnoredIds));
+ builder.setIgnoredIds(getAutofillIds(autofillIdResolver, mIgnoredIds));
}
if (mAuthenticationIds != null) {
- builder.setAuthentication(getAutofillIds(nodeResolver, mAuthenticationIds),
+ builder.setAuthentication(getAutofillIds(autofillIdResolver, mAuthenticationIds),
mAuthentication, mPresentation, mInlinePresentation);
}
if (mDisableDuration > 0) {
@@ -246,7 +263,7 @@
builder.setFieldClassificationIds(fieldIds);
} else if (mFieldClassificationIds != null) {
builder.setFieldClassificationIds(
- getAutofillIds(nodeResolver, mFieldClassificationIds));
+ getAutofillIds(autofillIdResolver, mFieldClassificationIds));
}
if (mExtras != null) {
builder.setClientState(mExtras);
@@ -610,7 +627,21 @@
/**
* Creates a new dataset, replacing the field ids by the real ids from the assist structure.
*/
- public Dataset asDataset(Function<String, ViewNode> nodeResolver) {
+ public Dataset asDatasetWithNodeResolver(Function<String, ViewNode> nodeResolver) {
+ return asDatasetWithAutofillIdResolver((id) -> {
+ ViewNode node = nodeResolver.apply(id);
+ if (node == null) {
+ throw new AssertionError("No node with resource id " + id);
+ }
+ return node.getAutofillId();
+ });
+ }
+
+ /**
+ * Creates a new dataset, replacing the field ids by the real ids from the assist structure.
+ */
+ public Dataset asDatasetWithAutofillIdResolver(
+ Function<String, AutofillId> autofillIdResolver) {
final Dataset.Builder builder = mPresentation != null
? new Dataset.Builder(mPresentation)
: new Dataset.Builder();
@@ -625,11 +656,11 @@
if (mFieldValues != null) {
for (Map.Entry<String, AutofillValue> entry : mFieldValues.entrySet()) {
final String id = entry.getKey();
- final ViewNode node = nodeResolver.apply(id);
- if (node == null) {
+
+ final AutofillId autofillId = autofillIdResolver.apply(id);
+ if (autofillId == null) {
throw new AssertionError("No node with resource id " + id);
}
- final AutofillId autofillId = node.getAutofillId();
final AutofillValue value = entry.getValue();
final RemoteViews presentation = mFieldPresentations.get(id);
final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(id);
@@ -950,6 +981,6 @@
}
public interface SaveInfoDecorator {
- void decorate(SaveInfo.Builder builder, Function<String, ViewNode> nodeResolver);
+ void decorate(SaveInfo.Builder builder, Function<String, AutofillId> nodeResolver);
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/ClientAutofillRequestCallback.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/ClientAutofillRequestCallback.java
new file mode 100644
index 0000000..b970785
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/ClientAutofillRequestCallback.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.testcore;
+
+import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.FAILURE;
+import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.NULL;
+import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.CONNECTION_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.RESPONSE_DELAY_MS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.service.autofill.AutofillService;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillContext;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveCallback;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillRequestCallback;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.compatibility.common.util.RetryableException;
+import com.android.compatibility.common.util.TestNameUtils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+
+/**
+ * Implements an {@link AutofillRequestCallback} for testing client suggestions behavior.
+ */
+public class ClientAutofillRequestCallback implements AutofillRequestCallback {
+ private static final String TAG = "ClientAutofillRequestCallback";
+ private final Handler mHandler;
+ private final @NonNull Function<String, AutofillId> mIdResolver;
+ private final Replier mReplier;
+
+ public ClientAutofillRequestCallback(@NonNull Handler handler,
+ @NonNull Function<String, AutofillId> idResolver) {
+ mHandler = handler;
+ mIdResolver = idResolver;
+ mReplier = new Replier(mIdResolver);
+ }
+
+ @Override
+ public void onFillRequest(InlineSuggestionsRequest inlineSuggestionsRequest,
+ CancellationSignal cancellationSignal, FillCallback callback) {
+
+ if (!TestNameUtils.isRunningTest()) {
+ Log.e(TAG, "onFillRequest(client) called after tests finished");
+ return;
+ }
+
+ mHandler.post(
+ () -> mReplier.onFillRequest(
+ cancellationSignal, callback, inlineSuggestionsRequest));
+ }
+
+ public Replier getReplier() {
+ return mReplier;
+ }
+
+ /**
+ * POJO representation of the contents of a
+ * {@link ClientAutofillRequestCallback#onFillRequest(InlineSuggestionsRequest,
+ * CancellationSignal, FillCallback)} that can be asserted at the end of a test case.
+ */
+ public static final class FillRequest {
+
+ public final CancellationSignal cancellationSignal;
+ public final FillCallback callback;
+ public final InlineSuggestionsRequest inlineRequest;
+
+ private FillRequest(CancellationSignal cancellationSignal, FillCallback callback,
+ InlineSuggestionsRequest inlineRequest) {
+ this.cancellationSignal = cancellationSignal;
+ this.callback = callback;
+ this.inlineRequest = inlineRequest;
+ }
+
+ @Override
+ public String toString() {
+ return "FillRequest[has inlineRequest=" + (inlineRequest != null) + "]";
+ }
+ }
+
+ /**
+ * Object used to answer a
+ * {@link AutofillRequestCallback#onFillRequest(InlineSuggestionsRequest, CancellationSignal,
+ * FillCallback)}
+ * on behalf of a unit test method.
+ */
+ public static final class Replier {
+ // TODO: refactor with InstrumentedAutoFillService$Replier
+
+ private final BlockingQueue<CannedFillResponse> mResponses = new LinkedBlockingQueue<>();
+ private final BlockingQueue<FillRequest> mFillRequests =
+ new LinkedBlockingQueue<>();
+
+ private List<Throwable> mExceptions;
+ private IntentSender mOnSaveIntentSender;
+ private String mAcceptedPackageName;
+
+ private Handler mHandler;
+
+ private boolean mReportUnhandledFillRequest = true;
+ private boolean mReportUnhandledSaveRequest = true;
+ private final @NonNull Function<String, AutofillId> mIdResolver;
+
+ private Replier(@NonNull Function<String, AutofillId> idResolver) {
+ mIdResolver = idResolver;
+ }
+
+ public void acceptRequestsFromPackage(String packageName) {
+ mAcceptedPackageName = packageName;
+ }
+
+ /**
+ * Gets the exceptions thrown asynchronously, if any.
+ */
+ @Nullable
+ public List<Throwable> getExceptions() {
+ return mExceptions;
+ }
+
+ private void addException(@Nullable Throwable e) {
+ if (e == null) return;
+
+ if (mExceptions == null) {
+ mExceptions = new ArrayList<>();
+ }
+ mExceptions.add(e);
+ }
+
+ /**
+ * Sets the expectation for the next {@code onFillRequest} as {@link FillResponse} with
+ * just one {@link Dataset}.
+ */
+ public Replier addResponse(
+ CannedFillResponse.CannedDataset dataset) {
+ return addResponse(new CannedFillResponse.Builder()
+ .addDataset(dataset)
+ .build());
+ }
+
+ /**
+ * Sets the expectation for the next {@code onFillRequest}.
+ */
+ public Replier addResponse(CannedFillResponse response) {
+ if (response == null) {
+ throw new IllegalArgumentException("Cannot be null - use NO_RESPONSE instead");
+ }
+ mResponses.add(response);
+ return this;
+ }
+
+ /**
+ * Sets the {@link IntentSender} that is passed to
+ * {@link SaveCallback#onSuccess(IntentSender)}.
+ */
+ public Replier setOnSave(IntentSender intentSender) {
+ mOnSaveIntentSender = intentSender;
+ return this;
+ }
+
+ /**
+ * Gets the next fill request, in the order received.
+ */
+ public FillRequest getNextFillRequest() {
+ FillRequest request;
+ try {
+ request = mFillRequests.poll(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException("Interrupted", e);
+ }
+ if (request == null) {
+ throw new RetryableException(FILL_TIMEOUT, "onFillRequest() not called");
+ }
+ return request;
+ }
+
+ /**
+ * Assets the client had received fill request.
+ */
+ public void assertReceivedRequest() {
+ getNextFillRequest();
+ }
+
+ /**
+ * Asserts that {@link AutofillRequestCallback#onFillRequest(InlineSuggestionsRequest,
+ * CancellationSignal, FillCallback)} was not called.
+ *
+ * <p>Should only be called in cases where it's not expected to be called, as it will
+ * sleep for a few ms.
+ */
+ public void assertOnFillRequestNotCalled() {
+ SystemClock.sleep(FILL_TIMEOUT.getMaxValue());
+ assertThat(mFillRequests).isEmpty();
+ }
+
+ /**
+ * Asserts all {@link AutofillService#onFillRequest(
+ * android.service.autofill.FillRequest, CancellationSignal, FillCallback) fill requests}
+ * received by the service were properly {@link #getNextFillRequest() handled} by the test
+ * case.
+ */
+ public void assertNoUnhandledFillRequests() {
+ if (mFillRequests.isEmpty()) return; // Good job, test case!
+
+ if (!mReportUnhandledFillRequest) {
+ // Just log, so it's not thrown again on @After if already thrown on main body
+ Log.d(TAG, "assertNoUnhandledFillRequests(): already reported, "
+ + "but logging just in case: " + mFillRequests);
+ return;
+ }
+
+ mReportUnhandledFillRequest = false;
+ throw new AssertionError(mFillRequests.size()
+ + " unhandled fill requests: " + mFillRequests);
+ }
+
+ /**
+ * Gets the current number of unhandled requests.
+ */
+ public int getNumberUnhandledFillRequests() {
+ return mFillRequests.size();
+ }
+
+ public void setHandler(Handler handler) {
+ mHandler = handler;
+ }
+
+ /**
+ * Resets its internal state.
+ */
+ public void reset() {
+ mResponses.clear();
+ mFillRequests.clear();
+ mExceptions = null;
+ mOnSaveIntentSender = null;
+ mAcceptedPackageName = null;
+ mReportUnhandledFillRequest = true;
+ mReportUnhandledSaveRequest = true;
+ }
+
+ public void onFillRequest(CancellationSignal cancellationSignal, FillCallback callback,
+ InlineSuggestionsRequest inlineSuggestionsRequest) {
+ try {
+ CannedFillResponse response = null;
+ try {
+ response = mResponses.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Interrupted getting CannedResponse: " + e);
+ Thread.currentThread().interrupt();
+ addException(e);
+ return;
+ }
+ if (response == null) {
+ Log.d(TAG, "response is null");
+ return;
+ }
+ if (response.getResponseType() == NULL) {
+ Log.d(TAG, "onFillRequest(): replying with null");
+ callback.onSuccess(null);
+ return;
+ }
+
+ if (response.getResponseType() == TIMEOUT) {
+ Log.d(TAG, "onFillRequest(): not replying at all");
+ return;
+ }
+
+ if (response.getResponseType() == FAILURE) {
+ Log.d(TAG, "onFillRequest(): replying with failure");
+ callback.onFailure("D'OH!");
+ return;
+ }
+
+ if (response.getResponseType() == CannedFillResponse.ResponseType.NO_MORE) {
+ Log.w(TAG, "onFillRequest(): replying with null when not expecting more");
+ addException(new IllegalStateException("got unexpected request"));
+ callback.onSuccess(null);
+ return;
+ }
+
+ final String failureMessage = response.getFailureMessage();
+ if (failureMessage != null) {
+ Log.v(TAG, "onFillRequest(): failureMessage = " + failureMessage);
+ callback.onFailure(failureMessage);
+ return;
+ }
+
+ final FillResponse fillResponse;
+ fillResponse = response.asFillResponseWithAutofillId(null, mIdResolver);
+
+ if (response.getResponseType() == CannedFillResponse.ResponseType.DELAY) {
+ mHandler.postDelayed(() -> {
+ Log.v(TAG,
+ "onFillRequest(): fillResponse = " + fillResponse);
+ callback.onSuccess(fillResponse);
+ // Add a fill request to let test case know response was sent.
+ Helper.offer(
+ mFillRequests,
+ new FillRequest(cancellationSignal, callback,
+ inlineSuggestionsRequest),
+ CONNECTION_TIMEOUT.ms());
+ }, RESPONSE_DELAY_MS);
+ } else {
+ Log.v(TAG, "onFillRequest(): fillResponse = " + fillResponse);
+ callback.onSuccess(fillResponse);
+ }
+ } catch (Throwable t) {
+ Log.d(TAG, "onFillRequest(): catch a Throwable: " + t);
+ addException(t);
+ } finally {
+ Helper.offer(
+ mFillRequests,
+ new FillRequest(cancellationSignal, callback,
+ inlineSuggestionsRequest),
+ CONNECTION_TIMEOUT.ms());
+ }
+ }
+
+ private void onSaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
+ List<String> datasetIds) {
+ Log.d(TAG, "onSaveRequest(): sender=" + mOnSaveIntentSender);
+
+ try {
+ if (mOnSaveIntentSender != null) {
+ callback.onSuccess(mOnSaveIntentSender);
+ } else {
+ callback.onSuccess();
+ }
+ } finally {
+ //TODO
+ }
+ }
+
+ private void dump(PrintWriter pw) {
+ pw.print("mResponses: "); pw.println(mResponses);
+ pw.print("mFillRequests: "); pw.println(mFillRequests);
+ pw.print("mExceptions: "); pw.println(mExceptions);
+ pw.print("mOnSaveIntentSender: "); pw.println(mOnSaveIntentSender);
+ pw.print("mAcceptedPackageName: "); pw.println(mAcceptedPackageName);
+ pw.print("mAcceptedPackageName: "); pw.println(mAcceptedPackageName);
+ pw.print("mReportUnhandledFillRequest: "); pw.println(mReportUnhandledSaveRequest);
+ }
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
index 21befa5..6149ec9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
@@ -814,39 +814,19 @@
* Creates an array of {@link AutofillId} mapped from the {@code structure} nodes with the given
* {@code resourceIds}.
*/
- public static AutofillId[] getAutofillIds(Function<String, ViewNode> nodeResolver,
+ public static AutofillId[] getAutofillIds(Function<String, AutofillId> autofillIdResolver,
String[] resourceIds) {
if (resourceIds == null) return null;
final AutofillId[] requiredIds = new AutofillId[resourceIds.length];
for (int i = 0; i < resourceIds.length; i++) {
final String resourceId = resourceIds[i];
- final ViewNode node = nodeResolver.apply(resourceId);
- if (node == null) {
- throw new AssertionError("No node with resourceId " + resourceId);
- }
- requiredIds[i] = node.getAutofillId();
-
+ requiredIds[i] = autofillIdResolver.apply(resourceId);
}
return requiredIds;
}
/**
- * Get an {@link AutofillId} mapped from the {@code structure} node with the given
- * {@code resourceId}.
- */
- public static AutofillId getAutofillId(Function<String, ViewNode> nodeResolver,
- String resourceId) {
- if (resourceId == null) return null;
-
- final ViewNode node = nodeResolver.apply(resourceId);
- if (node == null) {
- throw new AssertionError("No node with resourceId " + resourceId);
- }
- return node.getAutofillId();
- }
-
- /**
* Prevents the screen to rotate by itself
*/
public static void disableAutoRotation(UiBot uiBot) throws Exception {
diff --git a/tests/backup/AndroidTest.xml b/tests/backup/AndroidTest.xml
index ec0ade5..0e84662 100644
--- a/tests/backup/AndroidTest.xml
+++ b/tests/backup/AndroidTest.xml
@@ -33,11 +33,14 @@
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
<option name="test-file-name" value="CtsFullBackupApp.apk" />
<option name="test-file-name" value="CtsKeyValueBackupApp.apk" />
<option name="test-file-name" value="CtsBackupTestCases.apk" />
<option name="test-file-name" value="CtsPermissionBackupApp.apk" />
<option name="test-file-name" value="CtsPermissionBackupApp22.apk" />
+ <option name="test-file-name" value="CtsAppLocalesBackupApp1.apk" />
+ <option name="test-file-name" value="CtsAppLocalesBackupApp2.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/cts/backup" />
@@ -48,6 +51,8 @@
<option name="cleanup" value="true" />
<option name="push" value="CtsPermissionBackupApp.apk->/data/local/tmp/cts/backup/CtsPermissionBackupApp.apk" />
<option name="push" value="CtsPermissionBackupApp22.apk->/data/local/tmp/cts/backup/CtsPermissionBackupApp22.apk" />
+ <option name="push" value="CtsAppLocalesBackupApp1.apk->/data/local/tmp/cts/backup/CtsAppLocalesBackupApp1.apk" />
+ <option name="push" value="CtsAppLocalesBackupApp2.apk->/data/local/tmp/cts/backup/CtsAppLocalesBackupApp2.apk" />
</target_preparer>
<target_preparer class="android.cts.backup.BackupPreparer">
<option name="enable-backup-if-needed" value="true" />
diff --git a/tests/backup/app/Android.bp b/tests/backup/app/Android.bp
index da52a3e..341b841 100644
--- a/tests/backup/app/Android.bp
+++ b/tests/backup/app/Android.bp
@@ -36,7 +36,7 @@
"mts",
],
platform_apis: true,
- manifest: "fullbackup/AndroidManifest.xml"
+ manifest: "fullbackup/AndroidManifest.xml",
}
android_test_helper_app {
@@ -60,7 +60,7 @@
"mts",
],
platform_apis: true,
- manifest: "keyvalue/AndroidManifest.xml"
+ manifest: "keyvalue/AndroidManifest.xml",
}
android_test_helper_app {
@@ -84,7 +84,7 @@
"mts",
],
platform_apis: true,
- manifest: "permission/AndroidManifest.xml"
+ manifest: "permission/AndroidManifest.xml",
}
android_test_helper_app {
@@ -106,5 +106,53 @@
"mts",
],
platform_apis: true,
- manifest: "permission22/AndroidManifest.xml"
+ manifest: "permission22/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "CtsAppLocalesBackupApp1",
+ defaults: [
+ "cts_support_defaults",
+ "mts-target-sdk-version-current",
+ ],
+ min_sdk_version: "30",
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ ],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts",
+ ],
+ platform_apis: true,
+ manifest: "AppLocalesBackupApp1/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "CtsAppLocalesBackupApp2",
+ defaults: [
+ "cts_support_defaults",
+ "mts-target-sdk-version-current",
+ ],
+ min_sdk_version: "30",
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ ],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts",
+ ],
+ platform_apis: true,
+ manifest: "AppLocalesBackupApp2/AndroidManifest.xml",
}
diff --git a/tests/backup/app/AppLocalesBackupApp1/AndroidManifest.xml b/tests/backup/app/AppLocalesBackupApp1/AndroidManifest.xml
new file mode 100644
index 0000000..719714c
--- /dev/null
+++ b/tests/backup/app/AppLocalesBackupApp1/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cts.backup.applocalesbackupapp1">
+ <application
+ android:label="AppLocalesBackupApp1">
+ <uses-library android:name="android.test.runner" />
+ </application>
+</manifest>
diff --git a/tests/backup/app/AppLocalesBackupApp2/AndroidManifest.xml b/tests/backup/app/AppLocalesBackupApp2/AndroidManifest.xml
new file mode 100644
index 0000000..7bb7592
--- /dev/null
+++ b/tests/backup/app/AppLocalesBackupApp2/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cts.backup.applocalesbackupapp2">
+ <application
+ android:label="AppLocalesBackupApp2">
+ <uses-library android:name="android.test.runner" />
+ </application>
+</manifest>
diff --git a/tests/backup/app/fullbackup/AndroidManifest.xml b/tests/backup/app/fullbackup/AndroidManifest.xml
index 7f639f9..8a5e5f6 100644
--- a/tests/backup/app/fullbackup/AndroidManifest.xml
+++ b/tests/backup/app/fullbackup/AndroidManifest.xml
@@ -21,7 +21,8 @@
<application android:allowBackup="true"
android:backupAgent="FullBackupBackupAgent"
android:label="Android Backup CTS App"
- android:fullBackupOnly="true">
+ android:fullBackupOnly="true"
+ android:testOnly="true">
<uses-library android:name="android.test.runner"/>
diff --git a/tests/backup/app/keyvalue/AndroidManifest.xml b/tests/backup/app/keyvalue/AndroidManifest.xml
index c36d70c..937d344 100644
--- a/tests/backup/app/keyvalue/AndroidManifest.xml
+++ b/tests/backup/app/keyvalue/AndroidManifest.xml
@@ -20,7 +20,8 @@
<application android:allowBackup="true"
android:backupAgent="android.backup.app.KeyValueBackupAgent"
- android:label="Android Key Value Backup CTS App">
+ android:label="Android Key Value Backup CTS App"
+ android:testOnly="true">
<uses-library android:name="android.test.runner"/>
<activity android:name="android.backup.app.MainActivity"
diff --git a/tests/backup/src/android/backup/cts/AgentBindingTest.java b/tests/backup/src/android/backup/cts/AgentBindingTest.java
index 1a9fe01..e705e49 100644
--- a/tests/backup/src/android/backup/cts/AgentBindingTest.java
+++ b/tests/backup/src/android/backup/cts/AgentBindingTest.java
@@ -16,25 +16,57 @@
package android.backup.cts;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static org.testng.Assert.expectThrows;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.IBinder;
import android.platform.test.annotations.AppModeFull;
+import java.io.IOException;
import java.lang.reflect.Method;
+import java.util.Scanner;
@AppModeFull
public class AgentBindingTest extends BaseBackupCtsTest {
+ private static final String FULL_BACKUP_PACKAGE_NAME = "android.backup.app";
+ private static final String KEY_VALUE_BACKUP_PACKAGE_NAME = "android.backup.kvapp";
+ private static final ComponentName FULL_BACKUP_AGENT_NAME = ComponentName.createRelative(
+ FULL_BACKUP_PACKAGE_NAME, ".FullBackupBackupAgent");
+ private static final ComponentName KEY_VALUE_BACKUP_AGENT_NAME = ComponentName.createRelative(
+ KEY_VALUE_BACKUP_PACKAGE_NAME, "android.backup.app.KeyValueBackupAgent");
+ private static final int LOCAL_TRANSPORT_CONFORMING_FILE_SIZE = 5 * 1024;
+
private Context mContext;
+ // Save the states before running tests. Restore them after tests finished.
+ private int mFullBackupAgentEnabledState;
+ private int mKeyValueBackupAgentEnabledState;
+
@Override
protected void setUp() throws Exception {
super.setUp();
mContext = getInstrumentation().getTargetContext();
+ mFullBackupAgentEnabledState = mContext.getPackageManager().getComponentEnabledSetting(
+ FULL_BACKUP_AGENT_NAME);
+ mKeyValueBackupAgentEnabledState = mContext.getPackageManager().getComponentEnabledSetting(
+ KEY_VALUE_BACKUP_AGENT_NAME);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ setComponentEnabledSetting(FULL_BACKUP_AGENT_NAME, mFullBackupAgentEnabledState);
+ setComponentEnabledSetting(KEY_VALUE_BACKUP_AGENT_NAME, mKeyValueBackupAgentEnabledState);
}
public void testUnbindBackupAgent_isNotCallableFromCts() throws Exception {
@@ -51,6 +83,76 @@
expectThrows(Exception.class, () -> bindBackupAgent(mContext.getPackageName(), 0, 0));
}
+ public void testFullBackupAgentComponentDisabled() throws Exception {
+ if (!isBackupSupported()) {
+ return;
+ }
+ setComponentEnabledSetting(FULL_BACKUP_AGENT_NAME, COMPONENT_ENABLED_STATE_DISABLED);
+ // Make sure there's something to backup
+ createTestFileOfSize(FULL_BACKUP_PACKAGE_NAME, LOCAL_TRANSPORT_CONFORMING_FILE_SIZE);
+
+ runBackupAndAssertAgentError(FULL_BACKUP_PACKAGE_NAME);
+ }
+
+ public void testKeyValueBackupAgentComponentDisabled() throws Exception {
+ if (!isBackupSupported()) {
+ return;
+ }
+ setComponentEnabledSetting(KEY_VALUE_BACKUP_AGENT_NAME, COMPONENT_ENABLED_STATE_DISABLED);
+ // Make sure there's something to backup
+ createTestFileOfSize(KEY_VALUE_BACKUP_PACKAGE_NAME, LOCAL_TRANSPORT_CONFORMING_FILE_SIZE);
+
+ runBackupAndAssertAgentError(KEY_VALUE_BACKUP_PACKAGE_NAME);
+ }
+
+ private void setComponentEnabledSetting(ComponentName componentName, int enabledState)
+ throws IOException {
+ final StringBuilder cmd = new StringBuilder("pm ");
+ switch (enabledState) {
+ case COMPONENT_ENABLED_STATE_DEFAULT:
+ cmd.append("default-state ");
+ break;
+ case COMPONENT_ENABLED_STATE_ENABLED:
+ cmd.append("enable ");
+ break;
+ case COMPONENT_ENABLED_STATE_DISABLED:
+ cmd.append("disable ");
+ break;
+ case COMPONENT_ENABLED_STATE_DISABLED_USER:
+ cmd.append("disable-user ");
+ break;
+ case COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
+ cmd.append("disable-until-used ");
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid enabled state:" + enabledState);
+ }
+ cmd.append("--user cur ").append(componentName.flattenToString());
+ getBackupUtils().executeShellCommandSync(cmd.toString());
+ }
+
+ /**
+ * Parses the output of "bmgr backupnow" command and checks an agent error during
+ * backup / restore.
+ */
+ private void runBackupAndAssertAgentError(String packageName) throws IOException {
+ Scanner in = new Scanner(getBackupUtils().getBackupNowOutput(packageName));
+ boolean found = false;
+ while (in.hasNextLine()) {
+ String line = in.nextLine();
+
+ if (line.contains(packageName)) {
+ String result = line.split(":")[1].trim();
+ if ("Agent error".equals(result)) {
+ found = true;
+ break;
+ }
+ }
+ }
+ in.close();
+ assertTrue("Didn't find \'Agent error\' in the output", found);
+ }
+
private static void unbindBackupAgent(ApplicationInfo applicationInfo) throws Exception {
callActivityManagerMethod(
"unbindBackupAgent",
diff --git a/tests/backup/src/android/backup/cts/AppLocalesBackupTest.java b/tests/backup/src/android/backup/cts/AppLocalesBackupTest.java
new file mode 100644
index 0000000..defdb7c
--- /dev/null
+++ b/tests/backup/src/android/backup/cts/AppLocalesBackupTest.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.backup.cts;
+
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import android.Manifest;
+import android.app.LocaleManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.LocaleList;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AmUtils;
+import com.android.compatibility.common.util.ShellUtils;
+
+import org.junit.After;
+import org.junit.Before;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@AppModeFull
+public class AppLocalesBackupTest extends BaseBackupCtsTest {
+ private static final String APK_PATH = "/data/local/tmp/cts/backup/";
+ private static final String TEST_APP_APK_1 = APK_PATH + "CtsAppLocalesBackupApp1.apk";
+ private static final String TEST_APP_PACKAGE_1 =
+ "android.cts.backup.applocalesbackupapp1";
+
+ private static final String TEST_APP_APK_2 = APK_PATH + "CtsAppLocalesBackupApp2.apk";
+ private static final String TEST_APP_PACKAGE_2 =
+ "android.cts.backup.applocalesbackupapp2";
+ private static final String SYSTEM_PACKAGE = "android";
+
+ private static final LocaleList DEFAULT_LOCALES_1 = LocaleList.forLanguageTags("hi-IN,de-DE");
+ private static final LocaleList DEFAULT_LOCALES_2 = LocaleList.forLanguageTags("fr-CA");
+ private static final LocaleList EMPTY_LOCALES = LocaleList.getEmptyLocaleList();
+
+ // An identifier for the backup dataset. Since we're using localtransport, it's set to "1".
+ private static final String RESTORE_TOKEN = "1";
+
+ private Context mContext;
+ private LocaleManager mLocaleManager;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mContext = InstrumentationRegistry.getTargetContext();
+ mLocaleManager = mContext.getSystemService(LocaleManager.class);
+
+ install(TEST_APP_APK_1);
+ install(TEST_APP_APK_2);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ uninstall(TEST_APP_PACKAGE_1);
+ uninstall(TEST_APP_PACKAGE_2);
+ }
+
+ /**
+ * Tests the scenario where all apps are installed on the device when restore is triggered.
+ *
+ * <p>In this case, all the apps should have their locales restored as soon as the restore
+ * operation finishes. The only condition is that the apps should not have the locales set
+ * already before restore.
+ */
+ public void testBackupRestore_allAppsInstalledNoAppLocalesSet_restoresImmediately()
+ throws Exception {
+ if (!isBackupSupported()) {
+ return;
+ }
+ setAndBackupDefaultAppLocales();
+
+ resetAppLocales();
+
+ getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE);
+
+ assertLocalesForApp(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1);
+ assertLocalesForApp(TEST_APP_PACKAGE_2, DEFAULT_LOCALES_2);
+ }
+
+ /**
+ * Tests the scenario where the user sets the app-locales before the restore could be applied.
+ *
+ * <p>The locales from the backup data should be ignored in this case.
+ */
+ public void testBackupRestore_localeAlreadySet_doesNotRestore() throws Exception {
+ if (!isBackupSupported()) {
+ return;
+ }
+ setAndBackupDefaultAppLocales();
+
+ LocaleList newLocales = LocaleList.forLanguageTags("zh,hi");
+ setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, newLocales);
+ setApplicationLocalesAndVerify(TEST_APP_PACKAGE_2, EMPTY_LOCALES);
+
+ getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE);
+
+ // Should restore only for app_2.
+ assertLocalesForApp(TEST_APP_PACKAGE_1, newLocales);
+ assertLocalesForApp(TEST_APP_PACKAGE_2, DEFAULT_LOCALES_2);
+ }
+
+ /**
+ * Tests the scenario when some apps are installed after the restore finishes.
+ *
+ * <p>More specifically, this tests the lazy restore where the locales are fetched and
+ * restored from the stage file if the app is installed within a certain amount of time after
+ * the initial restore.
+ */
+ public void testBackupRestore_appInstalledAfterRestore_doesLazyRestore() throws Exception {
+ if (!isBackupSupported()) {
+ return;
+ }
+ setAndBackupDefaultAppLocales();
+
+ resetAppLocales();
+
+ uninstall(TEST_APP_PACKAGE_2);
+
+ getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE);
+
+ // Locales for App1 should be restored immediately since that's present already.
+ assertLocalesForApp(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1);
+
+ // This is to ensure there are no lingering broadcasts (could be from the setUp method
+ // where we are calling setApplicationLocales).
+ AmUtils.waitForBroadcastIdle();
+
+ BlockingBroadcastReceiver appSpecificLocaleBroadcastReceiver =
+ new BlockingBroadcastReceiver();
+ mContext.registerReceiver(appSpecificLocaleBroadcastReceiver,
+ new IntentFilter(Intent.ACTION_APPLICATION_LOCALE_CHANGED));
+
+ // Hold Manifest.permission.READ_APP_SPECIFIC_LOCALES while the broadcast is sent,
+ // so that we receive it.
+ runWithShellPermissionIdentity(() -> {
+ // Installation will trigger lazy restore, which internally calls setApplicationLocales
+ // which sends out the ACTION_APPLICATION_LOCALE_CHANGED broadcast.
+ install(TEST_APP_APK_2);
+ appSpecificLocaleBroadcastReceiver.await();
+ }, Manifest.permission.READ_APP_SPECIFIC_LOCALES);
+
+ appSpecificLocaleBroadcastReceiver.assertOneBroadcastReceived();
+ appSpecificLocaleBroadcastReceiver.assertReceivedBroadcastContains(TEST_APP_PACKAGE_2,
+ DEFAULT_LOCALES_2);
+
+ // Verify that lazy restore occurred upon package install.
+ assertLocalesForApp(TEST_APP_PACKAGE_2, DEFAULT_LOCALES_2);
+
+ // APP2's entry is removed from the stage file after restore so nothing should be restored
+ // when APP2 is installed for the second time.
+ uninstall(TEST_APP_PACKAGE_2);
+ install(TEST_APP_APK_2);
+ assertLocalesForApp(TEST_APP_PACKAGE_2, EMPTY_LOCALES);
+ }
+
+ /**
+ * Tests the scenario when an application is removed from the device.
+ *
+ * <p>The data for the uninstalled app should be removed from the next backup pass.
+ */
+ public void testBackupRestore_uninstallApp_deletesDataFromBackup() throws Exception {
+ if (!isBackupSupported()) {
+ return;
+ }
+ setAndBackupDefaultAppLocales();
+
+ // Uninstall an app and run the backup pass. The locales for the uninstalled app should
+ // be removed from the backup.
+ uninstall(TEST_APP_PACKAGE_2);
+ setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1);
+ getBackupUtils().backupNowAndAssertSuccess(SYSTEM_PACKAGE);
+
+ install(TEST_APP_APK_2);
+ // Remove app1's locales so that it can be restored.
+ setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, EMPTY_LOCALES);
+
+ getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE);
+
+ // Restores only app1's locales because app2's data is no longer present in the backup.
+ assertLocalesForApp(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1);
+ assertLocalesForApp(TEST_APP_PACKAGE_2, EMPTY_LOCALES);
+ }
+
+ // TODO(b/210593602): Add a test to check staged data removal after the retention period.
+
+ private void setApplicationLocalesAndVerify(String packageName, LocaleList locales)
+ throws Exception {
+ runWithShellPermissionIdentity(() ->
+ mLocaleManager.setApplicationLocales(packageName, locales),
+ Manifest.permission.CHANGE_CONFIGURATION);
+ assertLocalesForApp(packageName, locales);
+ }
+
+ /**
+ * Verifies that the locales are correctly set for another package
+ * by fetching locales of the app with a binder call.
+ */
+ private void assertLocalesForApp(String packageName,
+ LocaleList expectedLocales) throws Exception {
+ assertEquals(expectedLocales, getApplicationLocales(packageName));
+ }
+
+ private LocaleList getApplicationLocales(String packageName) throws Exception {
+ return callWithShellPermissionIdentity(() ->
+ mLocaleManager.getApplicationLocales(packageName),
+ Manifest.permission.READ_APP_SPECIFIC_LOCALES);
+ }
+
+ private void install(String apk) {
+ ShellUtils.runShellCommand("pm install -r " + apk);
+ }
+
+ private void uninstall(String packageName) {
+ ShellUtils.runShellCommand("pm uninstall " + packageName);
+ }
+
+ private void setAndBackupDefaultAppLocales() throws Exception {
+ setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1);
+ setApplicationLocalesAndVerify(TEST_APP_PACKAGE_2, DEFAULT_LOCALES_2);
+ // Backup the data for SYSTEM_PACKAGE which includes app-locales.
+ getBackupUtils().backupNowAndAssertSuccess(SYSTEM_PACKAGE);
+ }
+
+ private void resetAppLocales() throws Exception {
+ setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, EMPTY_LOCALES);
+ setApplicationLocalesAndVerify(TEST_APP_PACKAGE_2, EMPTY_LOCALES);
+ }
+
+ private static final class BlockingBroadcastReceiver extends BroadcastReceiver {
+ private CountDownLatch mLatch = new CountDownLatch(1);
+ private String mPackageName;
+ private LocaleList mLocales;
+ private int mCalls;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.hasExtra(Intent.EXTRA_PACKAGE_NAME)) {
+ mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ }
+ if (intent.hasExtra(Intent.EXTRA_LOCALE_LIST)) {
+ mLocales = intent.getParcelableExtra(Intent.EXTRA_LOCALE_LIST);
+ }
+ mCalls += 1;
+ mLatch.countDown();
+ }
+
+ public void await() throws Exception {
+ mLatch.await(/* timeout= */ 5, TimeUnit.SECONDS);
+ }
+
+ public void reset() {
+ mLatch = new CountDownLatch(1);
+ mCalls = 0;
+ mPackageName = null;
+ mLocales = null;
+ }
+
+ public void assertOneBroadcastReceived() {
+ assertEquals(1, mCalls);
+ }
+
+ /**
+ * Verifies that the broadcast received in the relevant apps have the correct information
+ * in the intent extras. It verifies the below extras:
+ * <ul>
+ * <li> {@link Intent#EXTRA_PACKAGE_NAME}
+ * <li> {@link Intent#EXTRA_LOCALE_LIST}
+ * </ul>
+ */
+ public void assertReceivedBroadcastContains(String expectedPackageName,
+ LocaleList expectedLocales) {
+ assertEquals(expectedPackageName, mPackageName);
+ assertEquals(expectedLocales, mLocales);
+ }
+ }
+}
diff --git a/tests/camera/AndroidManifest.xml b/tests/camera/AndroidManifest.xml
index 7426c65..ab3120a 100644
--- a/tests/camera/AndroidManifest.xml
+++ b/tests/camera/AndroidManifest.xml
@@ -89,6 +89,9 @@
android:screenOrientation="locked"
android:configChanges="keyboardHidden|orientation|screenSize">
</activity>
+ <activity android:name="android.hardware.camera2.cts.EmptyActivity"
+ android:label="EmptyActivity">
+ </activity>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/camera/api31test/src/android/camera/cts/api31test/SPerfClassTest.java b/tests/camera/api31test/src/android/camera/cts/api31test/SPerfClassTest.java
index cf02ebe..feb5567 100644
--- a/tests/camera/api31test/src/android/camera/cts/api31test/SPerfClassTest.java
+++ b/tests/camera/api31test/src/android/camera/cts/api31test/SPerfClassTest.java
@@ -206,9 +206,9 @@
}
/**
- * Check camera S Performance class requirement for JPEG sizes.
+ * Check JPEG size overrides for devices claiming S Performance class requirement via
+ * Version.MEDIA_PERFORMANCE_CLASS
*/
- @CddTest(requirement="7.5/H-1-8")
public void testSPerfClassJpegSizes() throws Exception {
boolean isSPerfClass = CameraTestUtils.isSPerfClass();
if (!isSPerfClass) {
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
index 620ccee..5640249 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
@@ -646,6 +646,7 @@
String expectedStr, String unExpectedStr) throws Exception {
String candidateId = expectedEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
java.util.concurrent.TimeUnit.MILLISECONDS);
+ assertNotNull("No " + expectedStr + " notice for expected ID " + expectedId, candidateId);
assertTrue("Received " + expectedStr + " notice for wrong ID, " +
"expected " + expectedId + ", got " + candidateId, expectedId.equals(candidateId));
assertTrue("Received > 1 " + expectedStr + " callback for id " + expectedId,
@@ -859,6 +860,43 @@
candidatePhysicalIds == null);
}
+ if (mAdoptShellPerm) {
+ // Open an arbitrary camera and make sure subsequently subscribed listener receives
+ // correct onCameraOpened/onCameraClosed callbacks
+
+ MockStateCallback mockListener = MockStateCallback.mock();
+ mCameraListener = new BlockingStateCallback(mockListener);
+
+ if (useExecutor) {
+ mCameraManager.openCamera(cameras[0], executor, mCameraListener);
+ } else {
+ mCameraManager.openCamera(cameras[0], mCameraListener, mHandler);
+ }
+
+ // Block until opened
+ mCameraListener.waitForState(BlockingStateCallback.STATE_OPENED,
+ CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
+ // Then verify only open happened, and close the camera
+ CameraDevice camera = verifyCameraStateOpened(cameras[0], mockListener);
+
+ if (useExecutor) {
+ mCameraManager.registerAvailabilityCallback(executor, ac);
+ } else {
+ mCameraManager.registerAvailabilityCallback(ac, mHandler);
+ }
+
+ // Verify that we see the expected 'onCameraOpened' event.
+ verifySingleAvailabilityCbsReceived(onCameraOpenedEventQueue,
+ onCameraClosedEventQueue, cameras[0], "onCameraOpened", "onCameraClosed");
+
+ camera.close();
+
+ mCameraListener.waitForState(BlockingStateCallback.STATE_CLOSED,
+ CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS);
+
+ verifySingleAvailabilityCbsReceived(onCameraClosedEventQueue,
+ onCameraOpenedEventQueue, cameras[0], "onCameraClosed", "onCameraOpened");
+ }
} // testCameraManagerListenerCallbacks
// Verify no LEGACY-level devices appear on devices first launched in the Q release or newer
diff --git a/tests/camera/src/android/hardware/camera2/cts/EmptyActivity.java b/tests/camera/src/android/hardware/camera2/cts/EmptyActivity.java
new file mode 100644
index 0000000..74e847e
--- /dev/null
+++ b/tests/camera/src/android/hardware/camera2/cts/EmptyActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+
+public class EmptyActivity extends Activity {
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState,
+ @Nullable PersistableBundle persistentState) {
+ super.onCreate(savedInstanceState, persistentState);
+ View view = new View(this);
+ view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ setContentView(view);
+ }
+}
diff --git a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index 28475ec..361bfda 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -16,7 +16,26 @@
package android.hardware.camera2.cts;
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
+import static android.hardware.camera2.cts.helpers.AssertHelpers.assertArrayContains;
+import static android.hardware.camera2.cts.helpers.AssertHelpers.assertArrayContainsAnyOf;
+import static android.hardware.camera2.cts.helpers.AssertHelpers.assertCollectionContainsAnyOf;
+import static android.hardware.cts.helpers.CameraUtils.matchParametersToCharacteristics;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
import android.content.Context;
+import android.content.Intent;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
@@ -32,44 +51,50 @@
import android.hardware.camera2.params.BlackLevelPattern;
import android.hardware.camera2.params.ColorSpaceTransform;
import android.hardware.camera2.params.DeviceStateSensorOrientationMap;
+import android.hardware.camera2.params.DynamicRangeProfiles;
import android.hardware.camera2.params.RecommendedStreamConfigurationMap;
import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.cts.helpers.CameraUtils;
import android.media.CamcorderProfile;
import android.media.ImageReader;
import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
import android.util.ArraySet;
-import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.Rational;
-import android.util.Range;
-import android.util.Size;
import android.util.Pair;
import android.util.Patterns;
+import android.util.Range;
+import android.util.Rational;
+import android.util.Size;
import android.view.Display;
import android.view.Surface;
import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.rule.ActivityTestRule;
+
+import androidx.test.InstrumentationRegistry;
import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import java.util.Set;
-import org.junit.runners.Parameterized;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-import static android.hardware.camera2.cts.helpers.AssertHelpers.*;
-import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
-import static android.hardware.cts.helpers.CameraUtils.matchParametersToCharacteristics;
-
-import static junit.framework.Assert.*;
-
-import static org.mockito.Mockito.*;
+import static android.hardware.camera2.cts.CameraTestUtils.MPC_REPORT_LOG_NAME;
+import static android.hardware.camera2.cts.CameraTestUtils.MPC_STREAM_NAME;
/**
* Extended tests for static camera characteristics.
@@ -142,6 +167,10 @@
private static final int HIGH_SPEED_FPS_LOWER_MIN = 30;
private static final int HIGH_SPEED_FPS_UPPER_MIN = 120;
+ @Rule
+ public final ActivityTestRule<EmptyActivity> mActivityRule = new ActivityTestRule<>(
+ EmptyActivity.class, false, false);
+
@Override
public void setUp() throws Exception {
super.setUp();
@@ -817,7 +846,7 @@
try {
RecommendedStreamConfigurationMap map = c.getRecommendedStreamConfigurationMap(
- RecommendedStreamConfigurationMap.USECASE_LOW_LATENCY_SNAPSHOT + 1);
+ RecommendedStreamConfigurationMap.USECASE_10BIT_OUTPUT + 1);
fail("Recommended configuration map shouldn't be available for invalid " +
"use case!");
} catch (IllegalArgumentException e) {
@@ -845,6 +874,9 @@
RecommendedStreamConfigurationMap lowLatencyConfig =
c.getRecommendedStreamConfigurationMap(
RecommendedStreamConfigurationMap.USECASE_LOW_LATENCY_SNAPSHOT);
+ RecommendedStreamConfigurationMap dynamic10BitOutputConfig =
+ c.getRecommendedStreamConfigurationMap(
+ RecommendedStreamConfigurationMap.USECASE_10BIT_OUTPUT);
if ((previewConfig == null) && (videoRecordingConfig == null) &&
(videoSnapshotConfig == null) && (snapshotConfig == null) &&
(rawConfig == null) && (zslConfig == null) && (lowLatencyConfig == null)) {
@@ -886,6 +918,13 @@
if (lowLatencyConfig != null) {
verifyRecommendedLowLatencyConfiguration(mAllCameraIds[i], c, lowLatencyConfig);
}
+
+ if (dynamic10BitOutputConfig != null) {
+ verifyCommonRecommendedConfiguration(mAllCameraIds[i], c, dynamic10BitOutputConfig,
+ /*checkNoInput*/ true, /*checkNoHighRes*/ false,
+ /*checkNoHighSpeed*/ false, /*checkNoPrivate*/ false,
+ /*checkNoDepth*/ true);
+ }
}
}
@@ -1778,6 +1817,76 @@
}
}
+ /**
+ * Check 10-Bit output capability
+ */
+ @Test
+ public void test10BitOutputCharacteristics() {
+ for (int i = 0; i < mAllCameraIds.length; i++) {
+ Log.i(TAG, "test10BitOutputCharacteristics: Testing camera ID " + mAllCameraIds[i]);
+
+ CameraCharacteristics c = mCharacteristics.get(i);
+ int[] capabilities = c.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+ assertNotNull("android.request.availableCapabilities must never be null",
+ capabilities);
+ boolean supports10BitOutput = arrayContains(capabilities,
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT);
+ if (!supports10BitOutput) {
+ continue;
+ }
+
+ DynamicRangeProfiles dynamicProfiles = c.get(
+ CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES);
+ mCollector.expectNotNull("Dynamic range profile must always be present in case " +
+ "of 10-bit capable devices!", dynamicProfiles);
+ Set<Integer> supportedProfiles = dynamicProfiles.getSupportedProfiles();
+ mCollector.expectTrue("Dynamic range profiles not present!",
+ !supportedProfiles.isEmpty());
+ // STANDARD and HLG10 must always be present in the supported profiles
+ mCollector.expectContains(supportedProfiles.toArray(), DynamicRangeProfiles.STANDARD);
+ mCollector.expectContains(supportedProfiles.toArray(), DynamicRangeProfiles.HLG10);
+
+ Integer recommendedProfile = c.get(
+ CameraCharacteristics.REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE);
+ assertNotNull(recommendedProfile);
+ mCollector.expectContains(supportedProfiles.toArray(), recommendedProfile);
+ mCollector.expectTrue("The recommended 10-bit dynamic range profile must " +
+ "not be the same as standard",
+ recommendedProfile != DynamicRangeProfiles.STANDARD);
+
+ // Verify constraints validity. For example if HLG10 advertises support for HDR10, then
+ // there shouldn't be any HDR10 constraints related to HLG10.
+ for (Integer profile : supportedProfiles) {
+ Set<Integer> currentConstraints =
+ dynamicProfiles.getProfileCaptureRequestConstraints(profile);
+ boolean isSameProfilePresent = false;
+ for (Integer concurrentProfile : currentConstraints) {
+ if (concurrentProfile == profile) {
+ isSameProfilePresent = true;
+ continue;
+ }
+ String msg = String.format("Dynamic profile %d supports profile %d " +
+ "in the same capture request, however profile %d is not" +
+ "advertised as supported!", profile, concurrentProfile,
+ concurrentProfile);
+ mCollector.expectTrue(msg, supportedProfiles.contains(concurrentProfile));
+
+ Set<Integer> supportedConstraints =
+ dynamicProfiles.getProfileCaptureRequestConstraints(concurrentProfile);
+ msg = String.format("Dynamic range profile %d advertises support " +
+ "for profile %d, however the opposite is not true!",
+ profile, concurrentProfile);
+ mCollector.expectTrue(msg, supportedConstraints.isEmpty() ||
+ supportedConstraints.contains(profile));
+ }
+
+ String msg = String.format("Dynamic profile %d not present in its advertised " +
+ "capture request constraints!", profile);
+ mCollector.expectTrue(msg, isSameProfilePresent || currentConstraints.isEmpty());
+ }
+ }
+ }
+
private void verifyLensCalibration(float[] poseRotation, float[] poseTranslation,
Integer poseReference, float[] cameraIntrinsics, float[] distortion,
Rect precorrectionArray, Integer facing) {
@@ -2567,28 +2676,45 @@
/**
* Check camera orientation against device orientation
*/
- @CddTest(requirement="7.5.5/C-1-1")
+ @AppModeFull(reason = "DeviceStateManager is not accessible to instant apps")
+ @CddTest(requirement = "7.5.5/C-1-1")
@Test
public void testCameraOrientationAlignedWithDevice() {
- WindowManager windowManager =
- (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- Display display = windowManager.getDefaultDisplay();
- DisplayMetrics metrics = new DisplayMetrics();
- display.getMetrics(metrics);
+ if (CameraUtils.isDeviceFoldable(mContext)) {
+ // CDD 7.5.5/C-1-1 does not apply to devices with folding displays as the display aspect
+ // ratios might change with the device's folding state.
+ // Skip this test in foldables until the CDD is updated to include foldables.
+ Log.i(TAG, "CDD 7.5.5/C-1-1 does not apply to foldables, skipping"
+ + " testCameraOrientationAlignedWithDevice");
+ return;
+ }
+
+ // Start the empty activty to check the display we're testing.
+ mActivityRule.launchActivity(new Intent());
+ Context foregroundActivity = mActivityRule.getActivity();
+
+ WindowManager windowManager = foregroundActivity.getSystemService(WindowManager.class);
+ assertNotNull("Could not get window manager for test activity.", windowManager);
+
+ WindowMetrics metrics = windowManager.getMaximumWindowMetrics();
+ Rect displayBounds = metrics.getBounds();
+ int widthPixels = displayBounds.width();
+ int heightPixels = displayBounds.height();
// For square screen, test is guaranteed to pass
- if (metrics.widthPixels == metrics.heightPixels) {
+ if (widthPixels == heightPixels) {
return;
}
// Handle display rotation
+ Display display = foregroundActivity.getDisplay();
int displayRotation = display.getRotation();
if (displayRotation == Surface.ROTATION_90 || displayRotation == Surface.ROTATION_270) {
- int tmp = metrics.widthPixels;
- metrics.widthPixels = metrics.heightPixels;
- metrics.heightPixels = tmp;
+ int tmp = widthPixels;
+ widthPixels = heightPixels;
+ heightPixels = tmp;
}
- boolean isDevicePortrait = metrics.widthPixels < metrics.heightPixels;
+ boolean isDevicePortrait = widthPixels < heightPixels;
for (int i = 0; i < mAllCameraIds.length; i++) {
CameraCharacteristics c = mCharacteristics.get(i);
@@ -2613,28 +2739,62 @@
boolean isCameraPortrait =
adjustedSensorSize.getWidth() < adjustedSensorSize.getHeight();
- assertFalse("Camera " + mAllCameraIds[i] + "'s long dimension must "
- + "align with screen's long dimension", isDevicePortrait^isCameraPortrait);
+
+ // device and camera orientation should either be both portrait, or both landscape
+ assertEquals("Camera " + mAllCameraIds[i] + "'s long dimension must "
+ + "align with screen's long dimension", isDevicePortrait, isCameraPortrait);
}
}
/**
+ * If meetPerfClass is true, return perfClassLevel.
+ * Otherwise, return NOT_MET.
+ */
+ private int updatePerfClassLevel(boolean meetPerfClass, int perfClassLevel) {
+ if (!meetPerfClass) {
+ return CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+ } else {
+ return perfClassLevel;
+ }
+ }
+
+ /**
+ * Update perf class level based on meetSPerfClass and meetRPerfClass.
+ */
+ private int updatePerfClassLevel(boolean meetSPerfClass, boolean meetRPerfClass,
+ int perfClassLevel) {
+ if (!meetRPerfClass) {
+ return CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+ } else if (!meetSPerfClass &&
+ perfClassLevel > CameraTestUtils.PERFORMANCE_CLASS_R) {
+ return CameraTestUtils.PERFORMANCE_CLASS_R;
+ }
+ return perfClassLevel;
+ }
+
+ /**
* Check camera characteristics for R and S Performance class requirements as specified
* in CDD camera section 7.5
*/
@Test
- @CddTest(requirement="7.5")
+ @CddTest(requirement="7.5/H-1-1,H-1-2,H-1-3,H-1-4,H-1-8")
public void testCameraPerfClassCharacteristics() throws Exception {
if (mAdoptShellPerm) {
// Skip test for system camera. Performance class is only applicable for public camera
// ids.
return;
}
- boolean isRPerfClass = CameraTestUtils.isRPerfClass();
- boolean isSPerfClass = CameraTestUtils.isSPerfClass();
- if (!isRPerfClass && !isSPerfClass) {
- return;
- }
+ boolean assertRPerfClass = CameraTestUtils.isRPerfClass();
+ boolean assertSPerfClass = CameraTestUtils.isSPerfClass();
+ boolean assertPerfClass = (assertRPerfClass || assertSPerfClass);
+
+ int perfClassLevelH11 = CameraTestUtils.PERFORMANCE_CLASS_CURRENT;
+ int perfClassLevelH12 = CameraTestUtils.PERFORMANCE_CLASS_CURRENT;
+ int perfClassLevelH13 = CameraTestUtils.PERFORMANCE_CLASS_CURRENT;
+ int perfClassLevelH14 = CameraTestUtils.PERFORMANCE_CLASS_CURRENT;
+ int perfClassLevelH18 = CameraTestUtils.PERFORMANCE_CLASS_CURRENT;
+
+ DeviceReportLog reportLog = new DeviceReportLog(MPC_REPORT_LOG_NAME, MPC_STREAM_NAME);
boolean hasPrimaryRear = false;
boolean hasPrimaryFront = false;
@@ -2663,78 +2823,145 @@
if (isPrimaryRear) {
hasPrimaryRear = true;
- mCollector.expectTrue("Primary rear camera resolution should be at least " +
- MIN_BACK_SENSOR_PERF_CLASS_RESOLUTION + " pixels, is "+
- sensorResolution,
- sensorResolution >= MIN_BACK_SENSOR_PERF_CLASS_RESOLUTION);
+ if (sensorResolution < MIN_BACK_SENSOR_PERF_CLASS_RESOLUTION) {
+ mCollector.expectTrue("Primary rear camera resolution should be at least " +
+ MIN_BACK_SENSOR_PERF_CLASS_RESOLUTION + " pixels, is "+
+ sensorResolution, !assertPerfClass);
+ perfClassLevelH11 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+ }
+ reportLog.addValue("rear camera resolution", sensorResolution,
+ ResultType.NEUTRAL, ResultUnit.NONE);
// 4K @ 30fps
boolean supportUHD = videoSizes.contains(UHD);
boolean supportDC4K = videoSizes.contains(DC4K);
- mCollector.expectTrue("Primary rear camera should support 4k video recording",
- supportUHD || supportDC4K);
- if (supportUHD || supportDC4K) {
+ reportLog.addValue("rear camera 4k support", supportUHD | supportDC4K,
+ ResultType.NEUTRAL, ResultUnit.NONE);
+ if (!supportUHD && !supportDC4K) {
+ mCollector.expectTrue("Primary rear camera should support 4k video recording",
+ !assertPerfClass);
+ perfClassLevelH11 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+ } else {
long minFrameDuration = config.getOutputMinFrameDuration(
android.media.MediaRecorder.class, supportDC4K ? DC4K : UHD);
- mCollector.expectTrue("Primary rear camera should support 4k video @ 30fps",
- minFrameDuration < (1e9 / 29.9));
+ reportLog.addValue("rear camera 4k frame duration", minFrameDuration,
+ ResultType.NEUTRAL, ResultUnit.NONE);
+ if (minFrameDuration >= (1e9 / 29.9)) {
+ mCollector.expectTrue("Primary rear camera should support 4k video @ 30fps",
+ !assertPerfClass);
+ perfClassLevelH11 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+ }
}
} else {
hasPrimaryFront = true;
- if (isSPerfClass) {
+ if (sensorResolution < MIN_FRONT_SENSOR_S_PERF_CLASS_RESOLUTION) {
+ mCollector.expectTrue("Primary front camera resolution should be at least " +
+ MIN_FRONT_SENSOR_S_PERF_CLASS_RESOLUTION + " pixels, is "+
+ sensorResolution, !assertSPerfClass);
+ perfClassLevelH12 = Math.min(
+ perfClassLevelH12, CameraTestUtils.PERFORMANCE_CLASS_R);
+ }
+ if (sensorResolution < MIN_FRONT_SENSOR_R_PERF_CLASS_RESOLUTION) {
mCollector.expectTrue("Primary front camera resolution should be at least " +
MIN_FRONT_SENSOR_S_PERF_CLASS_RESOLUTION + " pixels, is "+
- sensorResolution,
- sensorResolution >= MIN_FRONT_SENSOR_S_PERF_CLASS_RESOLUTION);
- } else {
- mCollector.expectTrue("Primary front camera resolution should be at least " +
- MIN_FRONT_SENSOR_R_PERF_CLASS_RESOLUTION + " pixels, is "+
- sensorResolution,
- sensorResolution >= MIN_FRONT_SENSOR_R_PERF_CLASS_RESOLUTION);
+ sensorResolution, !assertRPerfClass);
+ perfClassLevelH12 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
}
+ reportLog.addValue("front camera resolution", sensorResolution,
+ ResultType.NEUTRAL, ResultUnit.NONE);
+
// 1080P @ 30fps
boolean supportFULLHD = videoSizes.contains(FULLHD);
- mCollector.expectTrue("Primary front camera should support 1080P video recording",
- supportFULLHD);
- if (supportFULLHD) {
+ reportLog.addValue("front camera 1080p support", supportFULLHD,
+ ResultType.NEUTRAL, ResultUnit.NONE);
+ if (!supportFULLHD) {
+ mCollector.expectTrue(
+ "Primary front camera should support 1080P video recording",
+ !assertPerfClass);
+ perfClassLevelH12 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+ } else {
long minFrameDuration = config.getOutputMinFrameDuration(
android.media.MediaRecorder.class, FULLHD);
- mCollector.expectTrue("Primary front camera should support 1080P video @ 30fps",
- minFrameDuration < (1e9 / 29.9));
+ if (minFrameDuration >= (1e9 / 29.9)) {
+ mCollector.expectTrue(
+ "Primary front camera should support 1080P video @ 30fps",
+ !assertPerfClass);
+ perfClassLevelH12 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+ }
+ reportLog.addValue("front camera 1080p frame duration", minFrameDuration,
+ ResultType.NEUTRAL, ResultUnit.NONE);
}
}
- String facingString = hasPrimaryRear ? "rear" : "front";
+ String facingString = isPrimaryRear ? "rear" : "front";
// H-1-3
- if (isSPerfClass || (isRPerfClass && isPrimaryRear)) {
+ if (assertSPerfClass || (assertRPerfClass && isPrimaryRear)) {
mCollector.expectTrue("Primary " + facingString +
" camera should be at least FULL, but is " +
toStringHardwareLevel(staticInfo.getHardwareLevelChecked()),
staticInfo.isHardwareLevelAtLeastFull());
- } else {
+ } else if (assertRPerfClass) {
mCollector.expectTrue("Primary " + facingString +
" camera should be at least LIMITED, but is " +
toStringHardwareLevel(staticInfo.getHardwareLevelChecked()),
staticInfo.isHardwareLevelAtLeastLimited());
}
+ reportLog.addValue(facingString + " camera hardware level",
+ staticInfo.getHardwareLevelChecked(), ResultType.NEUTRAL, ResultUnit.NONE);
+ if (isPrimaryRear) {
+ perfClassLevelH13 = updatePerfClassLevel(staticInfo.isHardwareLevelAtLeastFull(),
+ perfClassLevelH13);
+ } else {
+ perfClassLevelH13 = updatePerfClassLevel(staticInfo.isHardwareLevelAtLeastFull(),
+ staticInfo.isHardwareLevelAtLeastLimited(), perfClassLevelH13);
+ }
+
// H-1-4
Integer timestampSource = c.get(CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE);
+ reportLog.addValue(facingString + " timestampSource",
+ timestampSource, ResultType.NEUTRAL, ResultUnit.NONE);
+ boolean realtimeTimestamp = (timestampSource != null &&
+ timestampSource.equals(CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME));
mCollector.expectTrue(
"Primary " + facingString + " camera should support real-time timestamp source",
- timestampSource != null &&
- timestampSource.equals(CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME));
+ !assertPerfClass || realtimeTimestamp);
+ perfClassLevelH14 = updatePerfClassLevel(realtimeTimestamp, perfClassLevelH14);
// H-1-8
- if (isSPerfClass && isPrimaryRear) {
+ if (isPrimaryRear) {
+ boolean supportRaw = staticInfo.isCapabilitySupported(RAW);
+ reportLog.addValue(facingString + " camera raw support",
+ supportRaw, ResultType.NEUTRAL, ResultUnit.NONE);
mCollector.expectTrue("Primary rear camera should support RAW capability",
- staticInfo.isCapabilitySupported(RAW));
+ !assertSPerfClass || supportRaw);
+ perfClassLevelH18 = updatePerfClassLevel(supportRaw, true /*R*/, perfClassLevelH18);
}
}
- mCollector.expectTrue("There must be a primary rear camera for performance class.",
- hasPrimaryRear);
- mCollector.expectTrue("There must be a primary front camera for performance class.",
- hasPrimaryFront);
+ if (!hasPrimaryRear) {
+ mCollector.expectTrue("There must be a primary rear camera for performance class.",
+ !assertPerfClass);
+ perfClassLevelH11 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+ }
+ if (!hasPrimaryFront) {
+ mCollector.expectTrue("There must be a primary front camera for performance class.",
+ !assertPerfClass);
+ perfClassLevelH12 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+ }
+
+ reportLog.addValue("Version", "0.0.1", ResultType.NEUTRAL, ResultUnit.NONE);
+ final String PERF_CLASS_REQ_NUM_PREFIX = "2.2.7.2/7.5/";
+ reportLog.addValue(PERF_CLASS_REQ_NUM_PREFIX + "H-1-1",
+ perfClassLevelH11, ResultType.NEUTRAL, ResultUnit.NONE);
+ reportLog.addValue(PERF_CLASS_REQ_NUM_PREFIX + "H-1-2",
+ perfClassLevelH12, ResultType.NEUTRAL, ResultUnit.NONE);
+ reportLog.addValue(PERF_CLASS_REQ_NUM_PREFIX + "H-1-3",
+ perfClassLevelH13, ResultType.NEUTRAL, ResultUnit.NONE);
+ reportLog.addValue(PERF_CLASS_REQ_NUM_PREFIX + "H-1-4",
+ perfClassLevelH14, ResultType.NEUTRAL, ResultUnit.NONE);
+ reportLog.addValue(PERF_CLASS_REQ_NUM_PREFIX + "H-1-8",
+ perfClassLevelH18, ResultType.NEUTRAL, ResultUnit.NONE);
+ reportLog.submit(InstrumentationRegistry.getInstrumentation());
}
/**
diff --git a/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java b/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java
index 9b84387..82760f0 100644
--- a/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java
@@ -17,6 +17,7 @@
package android.hardware.camera2.cts;
import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.cts.CameraTestUtils.HandlerExecutor;
import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
@@ -50,6 +51,7 @@
private static final int NUM_REGISTERS = 10;
private ArrayList<String> mFlashCameraIdList;
+ private ArrayList<String> mNoFlashCameraIdList;
@Override
public void setUp() throws Exception {
@@ -60,17 +62,101 @@
// initialize the list of cameras that have a flash unit so it won't interfere with
// flash tests.
mFlashCameraIdList = new ArrayList<String>();
+ mNoFlashCameraIdList = new ArrayList<String>();
for (String id : mCameraIdsUnderTest) {
StaticMetadata info =
new StaticMetadata(mCameraManager.getCameraCharacteristics(id),
CheckLevel.ASSERT, /*collector*/ null);
if (info.hasFlash()) {
mFlashCameraIdList.add(id);
+ } else {
+ mNoFlashCameraIdList.add(id);
}
}
}
@Test
+ public void testTurnOnTorchWithStrengthLevel() throws Exception {
+ if (mNoFlashCameraIdList.size() != 0) {
+ for (String id : mNoFlashCameraIdList) {
+ CameraCharacteristics pc = mCameraManager.getCameraCharacteristics(id);
+ assertNull(pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_DEFAULT_LEVEL));
+ assertNull(pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL));
+ }
+ }
+
+ if (mFlashCameraIdList.size() == 0)
+ return;
+
+ for (String id : mFlashCameraIdList) {
+ resetTorchModeStatus(id);
+ }
+
+ for (String id: mFlashCameraIdList) {
+ int maxLevel = 0;
+ int defaultLevel = 0;
+ int minLevel = 1;
+ CameraCharacteristics pc = mCameraManager.getCameraCharacteristics(id);
+ if (pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_DEFAULT_LEVEL) != null) {
+ defaultLevel = pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_DEFAULT_LEVEL);
+ }
+ if (pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL) != null) {
+ maxLevel = pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL);
+ }
+ if (maxLevel > 1) {
+ assertTrue(minLevel <= defaultLevel);
+ assertTrue(defaultLevel <= maxLevel);
+ int torchStrength = 0;
+ CameraManager.TorchCallback torchListener = mock(CameraManager.TorchCallback.class);
+ mCameraManager.registerTorchCallback(torchListener, mHandler);
+
+ mCameraManager.turnOnTorchWithStrengthLevel(id, maxLevel);
+ SystemClock.sleep(TORCH_DURATION_MS);
+ torchStrength = mCameraManager.getTorchStrengthLevel(id);
+ assertEquals(torchStrength, maxLevel);
+ // Calling with same value twice to verify onTorchStrengthLevelChanged()
+ // with maxLevel value is called only once.
+ mCameraManager.turnOnTorchWithStrengthLevel(id, maxLevel);
+ torchStrength = mCameraManager.getTorchStrengthLevel(id);
+ assertEquals(torchStrength, maxLevel);
+
+ mCameraManager.turnOnTorchWithStrengthLevel(id, defaultLevel);
+ torchStrength = mCameraManager.getTorchStrengthLevel(id);
+ assertEquals(torchStrength, defaultLevel);
+
+ mCameraManager.turnOnTorchWithStrengthLevel(id, minLevel);
+ torchStrength = mCameraManager.getTorchStrengthLevel(id);
+ assertEquals(torchStrength, minLevel);
+
+ // Turn off the torch and verify if the strength level gets
+ // reset to default level.
+ mCameraManager.setTorchMode(id, false);
+ torchStrength = mCameraManager.getTorchStrengthLevel(id);
+ assertEquals(torchStrength, defaultLevel);
+
+ // verify corrected numbers of callbacks
+ verify(torchListener, timeout(TORCH_TIMEOUT_MS).
+ times(1)).onTorchModeChanged(id, true);
+
+ verify(torchListener,timeout(TORCH_TIMEOUT_MS).
+ times(1)).onTorchStrengthLevelChanged(id, maxLevel);
+ verify(torchListener,timeout(TORCH_TIMEOUT_MS).
+ times(1)).onTorchStrengthLevelChanged(id, minLevel);
+ verify(torchListener,timeout(TORCH_TIMEOUT_MS).
+ times(1)).onTorchStrengthLevelChanged(id, defaultLevel);
+
+ verify(torchListener, timeout(TORCH_TIMEOUT_MS).
+ times(2)).onTorchModeChanged(id, false);
+
+ mCameraManager.unregisterTorchCallback(torchListener);
+ } else {
+ Log.i(TAG, "Torch strength level adjustment is not supported.");
+ }
+ }
+ }
+
+
+ @Test
public void testSetTorchModeOnOff() throws Exception {
if (mFlashCameraIdList.size() == 0)
return;
@@ -363,6 +449,8 @@
private String mCameraId;
private ArrayBlockingQueue<Integer> mStatusQueue =
new ArrayBlockingQueue<Integer>(QUEUE_CAPACITY);
+ private ArrayBlockingQueue<Integer> mTorchStrengthQueue =
+ new ArrayBlockingQueue<Integer>(QUEUE_CAPACITY);
public static final int STATUS_UNAVAILABLE = 0;
public static final int STATUS_OFF = 1;
@@ -397,6 +485,17 @@
}
@Override
+ public void onTorchStrengthLevelChanged(String cameraId, int newStrengthLevel) {
+ if (cameraId.equals(mCameraId)) {
+ try {
+ mTorchStrengthQueue.put(newStrengthLevel);
+ } catch (Throwable e) {
+ fail(e.getMessage());
+ }
+ }
+ }
+
+ @Override
public void onTorchModeChanged(String cameraId, boolean enabled) {
if (cameraId.equals(mCameraId)) {
Integer s;
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
index a852729..d45a91b 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
@@ -16,7 +16,21 @@
package android.hardware.camera2.cts;
-import android.content.Context;
+import static android.hardware.camera2.cts.CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.SESSION_READY_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
+import static android.hardware.camera2.cts.CameraTestUtils.dumpFile;
+import static android.hardware.camera2.cts.CameraTestUtils.getValueNotNull;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
@@ -27,6 +41,7 @@
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.hardware.DataSpace;
import android.hardware.HardwareBuffer;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
@@ -36,11 +51,13 @@
import android.hardware.camera2.cts.helpers.StaticMetadata;
import android.hardware.camera2.cts.rs.BitmapUtils;
import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+import android.hardware.camera2.params.DynamicRangeProfiles;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.Image.Plane;
import android.media.ImageReader;
+import android.media.ImageWriter;
import android.os.ConditionVariable;
import android.util.Log;
import android.util.Size;
@@ -48,25 +65,16 @@
import com.android.ex.camera2.blocking.BlockingSessionCallback;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.Test;
-
-import static android.hardware.camera2.cts.CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS;
-import static android.hardware.camera2.cts.CameraTestUtils.SESSION_READY_TIMEOUT_MS;
-import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
-import static android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
-import static android.hardware.camera2.cts.CameraTestUtils.dumpFile;
-import static android.hardware.camera2.cts.CameraTestUtils.getValueNotNull;
-import static com.google.common.truth.Truth.assertWithMessage;
-import static junit.framework.Assert.*;
-
/**
* <p>Basic test for ImageReader APIs. It uses CameraDevice as producer, camera
* sends the data to the surface provided by imageReader. Below image formats
@@ -223,7 +231,18 @@
try {
Log.v(TAG, "Testing YUV P010 capture for Camera " + id);
openDevice(id);
- bufferFormatTestByCamera(ImageFormat.YCBCR_P010, /*repeating*/false);
+ if (!mStaticInfo.isCapabilitySupported(CameraCharacteristics.
+ REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)) {
+ continue;
+ }
+ Set<Integer> availableProfiles =
+ mStaticInfo.getAvailableDynamicRangeProfilesChecked();
+ assertFalse("Absent dynamic range profiles", availableProfiles.isEmpty());
+ assertTrue("HLG10 not present in the available dynamic range profiles",
+ availableProfiles.contains(DynamicRangeProfiles.HLG10));
+
+ bufferFormatTestByCamera(ImageFormat.YCBCR_P010, /*repeating*/false,
+ DynamicRangeProfiles.HLG10);
} finally {
closeDevice(id);
}
@@ -404,6 +423,76 @@
}
}
+ @Test
+ public void testImageReaderBuilderSetHardwareBufferFormatAndDataSpace() throws Exception {
+ long usage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT;
+ try (
+ ImageReader reader = new ImageReader
+ .Builder(20, 45)
+ .setMaxImages(2)
+ .setDefaultHardwareBufferFormat(HardwareBuffer.RGB_888)
+ .setDefaultDataSpace(DataSpace.DATASPACE_BT709)
+ .setUsage(usage)
+ .build();
+ ImageWriter writer = ImageWriter.newInstance(reader.getSurface(), 1);
+ Image outputImage = writer.dequeueInputImage()
+ ) {
+ assertEquals(2, reader.getMaxImages());
+ assertEquals(usage, reader.getUsage());
+ assertEquals(HardwareBuffer.RGB_888, reader.getHardwareBufferFormat());
+
+ assertEquals(20, outputImage.getWidth());
+ assertEquals(45, outputImage.getHeight());
+ assertEquals(HardwareBuffer.RGB_888, outputImage.getFormat());
+ }
+ }
+
+ @Test
+ public void testImageReaderBuilderImageFormatOverride() throws Exception {
+ try (
+ ImageReader reader = new ImageReader
+ .Builder(20, 45)
+ .setImageFormat(ImageFormat.HEIC)
+ .setDefaultHardwareBufferFormat(HardwareBuffer.RGB_888)
+ .setDefaultDataSpace(DataSpace.DATASPACE_BT709)
+ .build();
+ ImageWriter writer = ImageWriter.newInstance(reader.getSurface(), 1);
+ Image outputImage = writer.dequeueInputImage()
+ ) {
+ assertEquals(1, reader.getMaxImages());
+ assertEquals(HardwareBuffer.USAGE_CPU_READ_OFTEN, reader.getUsage());
+ assertEquals(HardwareBuffer.RGB_888, reader.getHardwareBufferFormat());
+ assertEquals(DataSpace.DATASPACE_BT709, reader.getDataSpace());
+
+ assertEquals(20, outputImage.getWidth());
+ assertEquals(45, outputImage.getHeight());
+ assertEquals(HardwareBuffer.RGB_888, outputImage.getFormat());
+ }
+ }
+
+ @Test
+ public void testImageReaderBuilderSetImageFormat() throws Exception {
+ try (
+ ImageReader reader = new ImageReader
+ .Builder(20, 45)
+ .setMaxImages(2)
+ .setImageFormat(ImageFormat.YUV_420_888)
+ .build();
+ ImageWriter writer = ImageWriter.newInstance(reader.getSurface(), 1);
+ Image outputImage = writer.dequeueInputImage()
+ ) {
+ assertEquals(2, reader.getMaxImages());
+ assertEquals(ImageFormat.YUV_420_888, reader.getImageFormat());
+ assertEquals(HardwareBuffer.USAGE_CPU_READ_OFTEN, reader.getUsage());
+ // ImageFormat.YUV_420_888 hal dataspace is DATASPACE_JFIF
+ assertEquals(DataSpace.DATASPACE_JFIF, reader.getDataSpace());
+
+ assertEquals(20, outputImage.getWidth());
+ assertEquals(45, outputImage.getHeight());
+ assertEquals(ImageFormat.YUV_420_888, outputImage.getFormat());
+ }
+ }
+
/**
* Test two image stream (YUV420_888 and RAW_SENSOR) capture by using ImageReader.
*
@@ -1076,6 +1165,13 @@
/*checkSession*/ false, /*validateImageData*/ true);
}
+ private void bufferFormatTestByCamera(int format, boolean repeating, int dynamicRangeProfile)
+ throws Exception {
+ bufferFormatTestByCamera(format, /*setUsageFlag*/ false,
+ HardwareBuffer.USAGE_CPU_READ_OFTEN, repeating, /*checkSession*/ false,
+ /*validateImageData*/ true, /*physicalId*/null, dynamicRangeProfile);
+ }
+
private void bufferFormatTestByCamera(int format, boolean repeating, boolean checkSession)
throws Exception {
bufferFormatTestByCamera(format, /*setUsageFlag*/ false,
@@ -1090,9 +1186,17 @@
}
private void bufferFormatTestByCamera(int format, boolean setUsageFlag, long usageFlag,
+ boolean repeating, boolean checkSession, boolean validateImageData, String physicalId)
+ throws Exception {
+ bufferFormatTestByCamera(format, setUsageFlag, usageFlag, repeating, checkSession,
+ validateImageData, physicalId, DynamicRangeProfiles.STANDARD);
+ }
+
+ private void bufferFormatTestByCamera(int format, boolean setUsageFlag, long usageFlag,
// TODO: Consider having some sort of test configuration class passed to reduce the
// proliferation of parameters ?
- boolean repeating, boolean checkSession, boolean validateImageData, String physicalId)
+ boolean repeating, boolean checkSession, boolean validateImageData, String physicalId,
+ int dynamicRangeProfile)
throws Exception {
StaticMetadata staticInfo;
if (physicalId == null) {
@@ -1150,6 +1254,7 @@
if (physicalId != null) {
config.setPhysicalCameraId(physicalId);
}
+ config.setDynamicRangeProfile(dynamicRangeProfile);
outputConfigs.add(config);
CaptureRequest request = prepareCaptureRequestForConfigs(
outputConfigs, CameraDevice.TEMPLATE_PREVIEW).build();
diff --git a/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
index 93826cb..0cc1135 100644
--- a/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
@@ -16,7 +16,28 @@
package android.hardware.camera2.cts;
-import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static android.hardware.camera2.cts.CameraTestUtils.ImageDropperListener;
+import static android.hardware.camera2.cts.CameraTestUtils.PREVIEW_SIZE_BOUND;
+import static android.hardware.camera2.cts.CameraTestUtils.SESSION_CONFIGURE_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.SessionConfigSupport;
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
+import static android.hardware.camera2.cts.CameraTestUtils.assertFalse;
+import static android.hardware.camera2.cts.CameraTestUtils.assertNotNull;
+import static android.hardware.camera2.cts.CameraTestUtils.assertTrue;
+import static android.hardware.camera2.cts.CameraTestUtils.configureCameraSessionWithConfig;
+import static android.hardware.camera2.cts.CameraTestUtils.fail;
+import static android.hardware.camera2.cts.CameraTestUtils.getCropRegionForZoom;
+import static android.hardware.camera2.cts.CameraTestUtils.getMaxPreviewSize;
+import static android.hardware.camera2.cts.CameraTestUtils.getPreviewSizeBound;
+import static android.hardware.camera2.cts.CameraTestUtils.getSupportedPreviewSizes;
+import static android.hardware.camera2.cts.CameraTestUtils.isSessionConfigSupported;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.Intent;
@@ -24,24 +45,19 @@
import android.graphics.ImageFormat;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
-import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
-import android.hardware.camera2.CaptureFailure;
-import android.hardware.camera2.cts.CaptureResultTest;
import android.hardware.camera2.cts.helpers.StaticMetadata;
-import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
-import android.media.CamcorderProfile;
import android.media.Image;
import android.media.ImageReader;
import android.os.BatteryManager;
@@ -52,29 +68,24 @@
import android.util.Range;
import android.util.Size;
import android.util.SizeF;
-import android.view.Display;
-import android.view.Surface;
import android.view.WindowManager;
import com.android.compatibility.common.util.CddTest;
-import com.android.compatibility.common.util.Stat;
import com.android.ex.camera2.blocking.BlockingSessionCallback;
import com.android.ex.camera2.utils.StateWaiter;
-import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
import java.util.ArrayList;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-
-import org.junit.runners.Parameterized;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-import static org.mockito.Mockito.*;
+import java.util.concurrent.LinkedBlockingQueue;
/**
* Tests exercising logical camera setup, configuration, and usage.
@@ -941,24 +952,24 @@
continue;
}
StreamConfigurationMap configMap =
- properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
physicalConfigs.put(physicalCameraId, configMap);
physicalPreviewSizesMap.put(physicalCameraId,
getSupportedPreviewSizes(physicalCameraId, mCameraManager, PREVIEW_SIZE_BOUND));
}
// Find display size from window service.
- Context context = mActivityRule.getActivity().getApplicationContext();
+ Context context = mActivityRule.getActivity();
WindowManager windowManager =
(WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- Display display = windowManager.getDefaultDisplay();
+ Rect windowBounds = windowManager.getCurrentWindowMetrics().getBounds();
- int displayWidth = display.getWidth();
- int displayHeight = display.getHeight();
+ int windowWidth = windowBounds.width();
+ int windowHeight = windowBounds.height();
- if (displayHeight > displayWidth) {
- displayHeight = displayWidth;
- displayWidth = display.getHeight();
+ if (windowHeight > windowWidth) {
+ windowHeight = windowWidth;
+ windowWidth = windowBounds.height();
}
StreamConfigurationMap config = mStaticInfo.getCharacteristics().get(
@@ -966,27 +977,27 @@
for (Size previewSize : previewSizes) {
dualPhysicalCameraIds.clear();
// Skip preview sizes larger than screen size
- if (previewSize.getWidth() > displayWidth ||
- previewSize.getHeight() > displayHeight) {
+ if (previewSize.getWidth() > windowWidth
+ || previewSize.getHeight() > windowHeight) {
continue;
}
final long minFrameDuration = config.getOutputMinFrameDuration(
- ImageFormat.YUV_420_888, previewSize);
+ ImageFormat.YUV_420_888, previewSize);
ArrayList<String> supportedPhysicalCameras = new ArrayList<String>();
for (String physicalCameraId : physicalCameraIds) {
List<Size> physicalPreviewSizes = physicalPreviewSizesMap.get(physicalCameraId);
if (physicalPreviewSizes != null && physicalPreviewSizes.contains(previewSize)) {
- long minDurationPhysical =
- physicalConfigs.get(physicalCameraId).getOutputMinFrameDuration(
- ImageFormat.YUV_420_888, previewSize);
- if (minDurationPhysical <= minFrameDuration) {
+ long minDurationPhysical =
+ physicalConfigs.get(physicalCameraId).getOutputMinFrameDuration(
+ ImageFormat.YUV_420_888, previewSize);
+ if (minDurationPhysical <= minFrameDuration) {
dualPhysicalCameraIds.add(physicalCameraId);
if (dualPhysicalCameraIds.size() == 2) {
return previewSize;
}
- }
+ }
}
}
}
@@ -1308,10 +1319,9 @@
}
private double getScreenSizeInInches() {
- DisplayMetrics dm = new DisplayMetrics();
- mWindowManager.getDefaultDisplay().getMetrics(dm);
- double widthInInchesSquared = Math.pow(dm.widthPixels/dm.xdpi,2);
- double heightInInchesSquared = Math.pow(dm.heightPixels/dm.ydpi,2);
+ DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
+ double widthInInchesSquared = Math.pow(dm.widthPixels / dm.xdpi, 2);
+ double heightInInchesSquared = Math.pow(dm.heightPixels / dm.ydpi, 2);
return Math.sqrt(widthInInchesSquared + heightInInchesSquared);
}
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
index 492cc68..9e05266 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -16,58 +16,89 @@
package android.hardware.camera2.cts;
-import static android.hardware.camera2.cts.CameraTestUtils.*;
-import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.*;
+import static android.hardware.camera2.cts.CameraTestUtils.PREVIEW_SIZE_BOUND;
+import static android.hardware.camera2.cts.CameraTestUtils.SessionConfigSupport;
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
+import static android.hardware.camera2.cts.CameraTestUtils.SizeComparator;
+import static android.hardware.camera2.cts.CameraTestUtils.StreamCombinationTargets;
+import static android.hardware.camera2.cts.CameraTestUtils.assertEquals;
+import static android.hardware.camera2.cts.CameraTestUtils.assertNotNull;
+import static android.hardware.camera2.cts.CameraTestUtils.checkSessionConfigurationSupported;
+import static android.hardware.camera2.cts.CameraTestUtils.checkSessionConfigurationWithSurfaces;
+import static android.hardware.camera2.cts.CameraTestUtils.configureReprocessableCameraSession;
+import static android.hardware.camera2.cts.CameraTestUtils.fail;
+import static android.hardware.camera2.cts.CameraTestUtils.getAscendingOrderSizes;
+import static android.hardware.camera2.cts.CameraTestUtils.isSessionConfigSupported;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.JPEG;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.MAXIMUM;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.PREVIEW;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.PRIV;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.RAW;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.RECORD;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.VGA;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.YUV;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
import android.content.Context;
import android.graphics.ImageFormat;
+import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
-import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.cts.helpers.StaticMetadata;
import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+import android.hardware.camera2.params.DynamicRangeProfiles;
import android.hardware.camera2.params.InputConfiguration;
-import android.hardware.camera2.params.OisSample;
-import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.MandatoryStreamCombination;
import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation;
+import android.hardware.camera2.params.OisSample;
+import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.CamcorderProfile;
import android.media.Image;
import android.media.ImageReader;
import android.media.ImageWriter;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.Size;
-import android.view.Display;
import android.view.Surface;
import android.view.WindowManager;
+import android.view.WindowMetrics;
import com.android.ex.camera2.blocking.BlockingSessionCallback;
-import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Comparator;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.Iterator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-
-import org.junit.runners.Parameterized;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.*;
+import java.util.concurrent.LinkedBlockingQueue;
/**
* Tests exercising edge cases in camera setup, configuration, and usage.
@@ -469,6 +500,173 @@
}
/**
+ * Test for making sure the required 10-bit stream combinations work as expected.
+ * Since we have too many possible combinations between different 8-bit vs. 10-bit as well
+ * as 10-bit dynamic profiles and in order to maximize the coverage within some reasonable
+ * amount of iterations, the test case will configure 8-bit and 10-bit outputs randomly. In case
+ * we have 10-bit output, then the dynamic range profile will also be randomly picked.
+ */
+ @Test
+ public void testMandatory10BitStreamCombinations() throws Exception {
+ for (String id : mCameraIdsUnderTest) {
+ openDevice(id);
+ CameraCharacteristics chars = mStaticInfo.getCharacteristics();
+ if (!CameraTestUtils.hasCapability(
+ chars, CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)) {
+ Log.i(TAG, "Camera id " + id + " doesn't support 10-bit output, skip test");
+ closeDevice(id);
+ continue;
+ }
+ CameraCharacteristics.Key<MandatoryStreamCombination []> ck =
+ CameraCharacteristics.SCALER_MANDATORY_TEN_BIT_OUTPUT_STREAM_COMBINATIONS;
+
+ MandatoryStreamCombination[] combinations = chars.get(ck);
+ assertNotNull(combinations);
+
+ try {
+ for (MandatoryStreamCombination combination : combinations) {
+ Log.i(TAG, "Testing fixed mandatory 10-bit output stream combination: " +
+ combination.getDescription() + " on camera: " + id);
+ DynamicRangeProfiles profiles = mStaticInfo.getCharacteristics().get(
+ CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES);
+ assertNotNull(profiles);
+
+ // First we want to make sure that a fixed set of 10-bit streams
+ // is functional
+ for (Integer profile : profiles.getSupportedProfiles()) {
+ if (profile != DynamicRangeProfiles.STANDARD) {
+ ArrayList<Integer> testProfiles = new ArrayList<Integer>() {
+ { add(profile); } };
+ testMandatory10BitStreamCombination(id, combination, profiles,
+ testProfiles);
+ }
+ }
+
+ Log.i(TAG, "Testing random mandatory 10-bit output stream combination: " +
+ combination.getDescription() + " on camera: " + id);
+ // Next try out a random mix of standard 8-bit and 10-bit profiles.
+ // The number of possible combinations is quite big and testing them
+ // all on physical hardware can become unfeasible.
+ ArrayList<Integer> testProfiles = new ArrayList<>(
+ profiles.getSupportedProfiles());
+ testMandatory10BitStreamCombination(id, combination, profiles, testProfiles);
+ }
+ } finally {
+ closeDevice(id);
+ }
+ }
+ }
+
+ private void testMandatory10BitStreamCombination(String cameraId,
+ MandatoryStreamCombination combination, DynamicRangeProfiles profiles,
+ List<Integer> testProfiles) {
+ final int TIMEOUT_FOR_RESULT_MS = 1000;
+ final int MIN_RESULT_COUNT = 3;
+
+ // Setup outputs
+ List<OutputConfiguration> outputConfigs = new ArrayList<>();
+ List<Surface> outputSurfaces = new ArrayList<Surface>();
+ List<Surface> uhOutputSurfaces = new ArrayList<Surface>();
+ StreamCombinationTargets targets = new StreamCombinationTargets();
+
+ CameraTestUtils.setupConfigurationTargets(combination.getStreamsInformation(),
+ targets, outputConfigs, outputSurfaces, uhOutputSurfaces, MIN_RESULT_COUNT,
+ /*substituteY8*/ false, /*substituteHeic*/false,
+ /*physicalCameraId*/ null,
+ /*multiResStreamConfig*/null, mHandler,
+ testProfiles);
+
+ try {
+ checkSessionConfigurationSupported(mCamera, mHandler, outputConfigs,
+ /*inputConfig*/ null, SessionConfiguration.SESSION_REGULAR,
+ true/*defaultSupport*/,
+ String.format("Session configuration query from combination: %s failed",
+ combination.getDescription()));
+
+ createSessionByConfigs(outputConfigs);
+
+ boolean constraintPresent = false;
+ List<Surface> constrainedOutputs = new ArrayList<>(outputSurfaces);
+
+ while (!outputSurfaces.isEmpty()) {
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ // Check to see how many outputs can be combined in to a single request including
+ // the first output surface and respecting the advertised constraints
+ Iterator<OutputConfiguration> it = outputConfigs.iterator();
+ OutputConfiguration config = it.next();
+ HashSet<Integer> currentProfiles = new HashSet<>();
+ currentProfiles.add(config.getDynamicRangeProfile());
+ requestBuilder.addTarget(config.getSurface());
+ outputSurfaces.remove(config.getSurface());
+ it.remove();
+ while (it.hasNext()) {
+ config = it.next();
+ Integer currentProfile = config.getDynamicRangeProfile();
+ Set<Integer> newLimitations = profiles.getProfileCaptureRequestConstraints(
+ currentProfile);
+ if (newLimitations.isEmpty() || (newLimitations.containsAll(currentProfiles))) {
+ currentProfiles.add(currentProfile);
+ requestBuilder.addTarget(config.getSurface());
+ outputSurfaces.remove(config.getSurface());
+ it.remove();
+ } else if (!constraintPresent && !newLimitations.isEmpty() &&
+ !newLimitations.containsAll(currentProfiles)) {
+ constraintPresent = true;
+ }
+ }
+
+ CaptureRequest request = requestBuilder.build();
+ CameraCaptureSession.CaptureCallback mockCaptureCallback =
+ mock(CameraCaptureSession.CaptureCallback.class);
+ mCameraSession.capture(request, mockCaptureCallback, mHandler);
+ verify(mockCaptureCallback,
+ timeout(TIMEOUT_FOR_RESULT_MS).atLeastOnce())
+ .onCaptureCompleted(
+ eq(mCameraSession),
+ eq(request),
+ isA(TotalCaptureResult.class));
+
+ verify(mockCaptureCallback, never()).
+ onCaptureFailed(
+ eq(mCameraSession),
+ eq(request),
+ isA(CaptureFailure.class));
+ }
+
+ if (constraintPresent) {
+ // Capture requests that include output surfaces with dynamic range profiles that
+ // cannot be combined must throw a corresponding exception
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ for (Surface s : constrainedOutputs) {
+ requestBuilder.addTarget(s);
+ }
+
+ CaptureRequest request = requestBuilder.build();
+ CameraCaptureSession.CaptureCallback mockCaptureCallback =
+ mock(CameraCaptureSession.CaptureCallback.class);
+ try {
+ mCameraSession.capture(request, mockCaptureCallback, mHandler);
+ fail("Capture request to outputs with incompatible dynamic range profiles "
+ + "must always fail!");
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+ }
+
+ Log.i(TAG, String.format("Done with camera %s, combination: %s, closing session",
+ cameraId, combination.getDescription()));
+ } catch (Throwable e) {
+ mCollector.addMessage(
+ String.format("Closing down for combination: %s failed due to: %s",
+ combination.getDescription(), e.getMessage()));
+ }
+
+ targets.close();
+ }
+
+ /**
* Test for making sure the required reprocess input/output combinations for each hardware
* level and capability work as expected.
*/
@@ -2693,32 +2891,34 @@
private static Size getMaxPreviewSize(Context context, String cameraId) {
try {
- WindowManager windowManager =
- (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- Display display = windowManager.getDefaultDisplay();
+ WindowManager windowManager = context.getSystemService(WindowManager.class);
+ assertNotNull("Could not find WindowManager service.", windowManager);
- int width = display.getWidth();
- int height = display.getHeight();
+ WindowMetrics windowMetrics = windowManager.getCurrentWindowMetrics();
+ Rect windowBounds = windowMetrics.getBounds();
+
+ int width = windowBounds.width();
+ int height = windowBounds.height();
if (height > width) {
height = width;
- width = display.getHeight();
+ width = windowBounds.height();
}
- CameraManager camMgr =
- (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+ CameraManager camMgr = context.getSystemService(CameraManager.class);
List<Size> orderedPreviewSizes = CameraTestUtils.getSupportedPreviewSizes(
- cameraId, camMgr, PREVIEW_SIZE_BOUND);
+ cameraId, camMgr, PREVIEW_SIZE_BOUND);
if (orderedPreviewSizes != null) {
for (Size size : orderedPreviewSizes) {
if (width >= size.getWidth() &&
- height >= size.getHeight())
+ height >= size.getHeight()) {
return size;
+ }
}
}
} catch (Exception e) {
- Log.e(TAG, "getMaxPreviewSize Failed. "+e.toString());
+ Log.e(TAG, "getMaxPreviewSize Failed. " + e);
}
return PREVIEW_SIZE_BOUND;
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
index a588c3e..e4c6246 100644
--- a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
@@ -433,6 +433,7 @@
case REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING:
case REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA:
case REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME:
+ case REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT:
// Tested in ExtendedCameraCharacteristicsTest
return;
case REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA:
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 5576780..966de25 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
@@ -16,10 +16,25 @@
package android.hardware.camera2.cts.testcases;
-import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static android.hardware.camera2.cts.CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.PREVIEW_SIZE_BOUND;
+import static android.hardware.camera2.cts.CameraTestUtils.SESSION_CLOSE_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.SESSION_CONFIGURE_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.SESSION_READY_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.assertFalse;
+import static android.hardware.camera2.cts.CameraTestUtils.assertNotNull;
+import static android.hardware.camera2.cts.CameraTestUtils.assertNull;
+import static android.hardware.camera2.cts.CameraTestUtils.assertTrue;
+import static android.hardware.camera2.cts.CameraTestUtils.checkSessionConfigurationSupported;
+import static android.hardware.camera2.cts.CameraTestUtils.configureCameraSession;
+import static android.hardware.camera2.cts.CameraTestUtils.configureCameraSessionWithConfig;
+import static android.hardware.camera2.cts.CameraTestUtils.getPreviewSizeBound;
+import static android.hardware.camera2.cts.CameraTestUtils.getSupportedPreviewSizes;
+import static android.hardware.camera2.cts.CameraTestUtils.isSessionConfigSupported;
-import static com.android.ex.camera2.blocking.BlockingSessionCallback.*;
-import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
+import static com.android.ex.camera2.blocking.BlockingSessionCallback.SESSION_CLOSED;
+import static com.android.ex.camera2.blocking.BlockingSessionCallback.SESSION_READY;
+import static com.android.ex.camera2.blocking.BlockingStateCallback.STATE_CLOSED;
import android.app.Activity;
import android.content.Context;
@@ -28,13 +43,11 @@
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraCaptureSession;
-import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
-import android.hardware.camera2.cts.CameraTestUtils;
import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.cts.Camera2MultiViewCtsActivity;
+import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
import android.hardware.camera2.cts.helpers.StaticMetadata;
import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
import android.hardware.camera2.params.OutputConfiguration;
@@ -43,7 +56,6 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
-import android.os.SystemClock;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
@@ -58,9 +70,6 @@
import junit.framework.Assert;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import java.util.Arrays;
@@ -141,7 +150,8 @@
protected void updatePreviewDisplayRotation(Size previewSize, TextureView textureView) {
int rotationDegrees = 0;
Camera2MultiViewCtsActivity activity = (Camera2MultiViewCtsActivity) mActivity;
- int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
+
+ int displayRotation = activity.getDisplay().getRotation();
Configuration config = activity.getResources().getConfiguration();
// Get UI display rotation
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
index af15ff0..e4def35 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -16,7 +16,6 @@
package android.hardware.camera2.cts;
-import androidx.annotation.NonNull;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
@@ -25,45 +24,47 @@
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
-import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.MultiResolutionImageReader;
+import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.cts.helpers.CameraErrorCollector;
import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.params.DynamicRangeProfiles;
import android.hardware.camera2.params.InputConfiguration;
-import android.hardware.camera2.TotalCaptureResult;
-import android.hardware.cts.helpers.CameraUtils;
-import android.hardware.camera2.params.MeteringRectangle;
-import android.hardware.camera2.params.MandatoryStreamCombination;
import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation;
+import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap;
import android.hardware.camera2.params.MultiResolutionStreamInfo;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.cts.helpers.CameraUtils;
import android.location.Location;
import android.location.LocationManager;
import android.media.ExifInterface;
import android.media.Image;
+import android.media.Image.Plane;
import android.media.ImageReader;
import android.media.ImageWriter;
-import android.media.Image.Plane;
import android.os.Build;
import android.os.ConditionVariable;
import android.os.Handler;
import android.util.Log;
import android.util.Pair;
-import android.util.Size;
import android.util.Range;
-import android.view.Display;
+import android.util.Size;
import android.view.Surface;
import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.annotation.NonNull;
import com.android.ex.camera2.blocking.BlockingCameraManager;
import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
@@ -79,6 +80,8 @@
import java.io.IOException;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -88,14 +91,13 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Random;
import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
+import java.util.concurrent.atomic.AtomicLong;
/**
* A package private utility class for wrapping up the camera2 cts test common utility functions
@@ -139,6 +141,8 @@
public static final String OFFLINE_CAMERA_ID = "offline_camera_id";
public static final String REPORT_LOG_NAME = "CtsCameraTestCases";
+ public static final String MPC_REPORT_LOG_NAME = "MediaPerformanceClassLogs";
+ public static final String MPC_STREAM_NAME = "CameraCts";
private static final int EXIF_DATETIME_LENGTH = 19;
private static final int EXIF_DATETIME_ERROR_MARGIN_SEC = 60;
@@ -236,6 +240,8 @@
public List<ImageReader> mRawTargets = new ArrayList<>();
public List<ImageReader> mHeicTargets = new ArrayList<>();
public List<ImageReader> mDepth16Targets = new ArrayList<>();
+ public List<ImageReader> mP010Targets = new ArrayList<>();
+
public List<MultiResolutionImageReader> mPrivMultiResTargets = new ArrayList<>();
public List<MultiResolutionImageReader> mJpegMultiResTargets = new ArrayList<>();
@@ -264,6 +270,9 @@
for (ImageReader target : mDepth16Targets) {
target.close();
}
+ for (ImageReader target : mP010Targets) {
+ target.close();
+ }
for (MultiResolutionImageReader target : mPrivMultiResTargets) {
target.close();
@@ -284,7 +293,8 @@
List<OutputConfiguration> outputConfigs, List<Surface> outputSurfaces,
int format, Size targetSize, int numBuffers, String overridePhysicalCameraId,
MultiResolutionStreamConfigurationMap multiResStreamConfig,
- boolean createMultiResiStreamConfig, ImageDropperListener listener, Handler handler) {
+ boolean createMultiResiStreamConfig, ImageDropperListener listener, Handler handler,
+ int dynamicRangeProfile) {
if (createMultiResiStreamConfig) {
Collection<MultiResolutionStreamInfo> multiResolutionStreams =
multiResStreamConfig.getOutputInfo(format);
@@ -319,6 +329,7 @@
if (overridePhysicalCameraId != null) {
config.setPhysicalCameraId(overridePhysicalCameraId);
}
+ config.setDynamicRangeProfile(dynamicRangeProfile);
outputConfigs.add(config);
outputSurfaces.add(config.getSurface());
targets.mPrivTargets.add(target);
@@ -330,6 +341,7 @@
if (overridePhysicalCameraId != null) {
config.setPhysicalCameraId(overridePhysicalCameraId);
}
+ config.setDynamicRangeProfile(dynamicRangeProfile);
outputConfigs.add(config);
outputSurfaces.add(config.getSurface());
@@ -352,6 +364,9 @@
case ImageFormat.DEPTH16:
targets.mDepth16Targets.add(target);
break;
+ case ImageFormat.YCBCR_P010:
+ targets.mP010Targets.add(target);
+ break;
default:
fail("Unknown/Unsupported output format " + format);
}
@@ -377,7 +392,29 @@
List<Surface> outputSurfaces, List<Surface> uhSurfaces, int numBuffers,
boolean substituteY8, boolean substituteHeic, String overridePhysicalCameraId,
MultiResolutionStreamConfigurationMap multiResStreamConfig, Handler handler) {
+ setupConfigurationTargets(streamsInfo, targets, outputConfigs, outputSurfaces, uhSurfaces,
+ numBuffers, substituteY8, substituteHeic, overridePhysicalCameraId,
+ multiResStreamConfig, handler, /*dynamicRangeProfiles*/ null);
+ }
+ public static void setupConfigurationTargets(List<MandatoryStreamInformation> streamsInfo,
+ StreamCombinationTargets targets,
+ List<OutputConfiguration> outputConfigs,
+ List<Surface> outputSurfaces, List<Surface> uhSurfaces, int numBuffers,
+ boolean substituteY8, boolean substituteHeic, String overridePhysicalCameraId,
+ MultiResolutionStreamConfigurationMap multiResStreamConfig, Handler handler,
+ List<Integer> dynamicRangeProfiles) {
+
+ Random rnd = new Random();
+ // 10-bit output capable streams will use a fixed dynamic range profile in case
+ // dynamicRangeProfiles.size() == 1 or random in case dynamicRangeProfiles.size() > 1
+ boolean use10BitRandomProfile = (dynamicRangeProfiles != null) &&
+ (dynamicRangeProfiles.size() > 1);
+ if (use10BitRandomProfile) {
+ Long seed = rnd.nextLong();
+ Log.i(TAG, "Random seed used for selecting 10-bit output: " + seed);
+ rnd.setSeed(seed);
+ }
ImageDropperListener imageDropperListener = new ImageDropperListener();
List<Surface> chosenSurfaces;
for (MandatoryStreamInformation streamInfo : streamsInfo) {
@@ -394,6 +431,19 @@
} else if (substituteHeic && (format == ImageFormat.JPEG)) {
format = ImageFormat.HEIC;
}
+
+ int dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+ if (streamInfo.is10BitCapable() && use10BitRandomProfile) {
+ boolean override10bit = rnd.nextBoolean();
+ if (!override10bit) {
+ dynamicRangeProfile = dynamicRangeProfiles.get(rnd.nextInt(
+ dynamicRangeProfiles.size()));
+ format = streamInfo.get10BitFormat();
+ }
+ } else if (streamInfo.is10BitCapable() && (dynamicRangeProfiles != null)) {
+ dynamicRangeProfile = dynamicRangeProfiles.get(0);
+ format = streamInfo.get10BitFormat();
+ }
Size[] availableSizes = new Size[streamInfo.getAvailableSizes().size()];
availableSizes = streamInfo.getAvailableSizes().toArray(availableSizes);
Size targetSize = CameraTestUtils.getMaxSize(availableSizes);
@@ -405,13 +455,15 @@
case ImageFormat.PRIVATE:
case ImageFormat.JPEG:
case ImageFormat.YUV_420_888:
+ case ImageFormat.YCBCR_P010:
case ImageFormat.Y8:
case ImageFormat.HEIC:
case ImageFormat.DEPTH16:
{
configureTarget(targets, outputConfigs, chosenSurfaces, format,
targetSize, numBuffers, overridePhysicalCameraId, multiResStreamConfig,
- createMultiResReader, imageDropperListener, handler);
+ createMultiResReader, imageDropperListener, handler,
+ dynamicRangeProfile);
break;
}
case ImageFormat.RAW_SENSOR: {
@@ -421,7 +473,7 @@
configureTarget(targets, outputConfigs, chosenSurfaces, format,
targetSize, numBuffers, overridePhysicalCameraId,
multiResStreamConfig, createMultiResReader, imageDropperListener,
- handler);
+ handler, dynamicRangeProfile);
}
break;
}
@@ -3426,21 +3478,23 @@
}
public static Size getPreviewSizeBound(WindowManager windowManager, Size bound) {
- Display display = windowManager.getDefaultDisplay();
+ WindowMetrics windowMetrics = windowManager.getCurrentWindowMetrics();
+ Rect windowBounds = windowMetrics.getBounds();
- int width = display.getWidth();
- int height = display.getHeight();
+ int windowHeight = windowBounds.height();
+ int windowWidth = windowBounds.width();
- if (height > width) {
- height = width;
- width = display.getHeight();
+ if (windowHeight > windowWidth) {
+ windowHeight = windowWidth;
+ windowWidth = windowBounds.height();
}
- if (bound.getWidth() <= width &&
- bound.getHeight() <= height)
+ if (bound.getWidth() <= windowWidth
+ && bound.getHeight() <= windowHeight) {
return bound;
- else
- return new Size(width, height);
+ } else {
+ return new Size(windowWidth, windowHeight);
+ }
}
/**
@@ -3740,8 +3794,10 @@
return zoomRatios;
}
- private static final int PERFORMANCE_CLASS_R = Build.VERSION_CODES.R;
- private static final int PERFORMANCE_CLASS_S = Build.VERSION_CODES.R + 1;
+ public static final int PERFORMANCE_CLASS_NOT_MET = 0;
+ public static final int PERFORMANCE_CLASS_R = Build.VERSION_CODES.R;
+ public static final int PERFORMANCE_CLASS_S = Build.VERSION_CODES.R + 1;
+ public static final int PERFORMANCE_CLASS_CURRENT = PERFORMANCE_CLASS_S;
/**
* Check whether this mobile device is R performance class as defined in CDD
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 1930b33..5e47542 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
@@ -24,8 +24,10 @@
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.cts.CameraTestUtils;
+import android.hardware.camera2.params.DynamicRangeProfiles;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.params.Capability;
+import android.util.ArraySet;
import android.util.Range;
import android.util.Size;
import android.util.Log;
@@ -77,7 +79,7 @@
// Last defined capability enum, for iterating over all of them
public static final int LAST_CAPABILITY_ENUM =
- CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING;
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT;
// Access via getAeModeName() to account for vendor extensions
public static final String[] AE_MODE_NAMES = new String[] {
@@ -792,6 +794,22 @@
}
/**
+ * Get and check the available dynamic range profiles.
+ *
+ * @return the available dynamic range profiles
+ */
+ public Set<Integer> getAvailableDynamicRangeProfilesChecked() {
+ DynamicRangeProfiles profiles = mCharacteristics.get(
+ CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES);
+
+ if (profiles == null) {
+ return new ArraySet<Integer>();
+ }
+
+ return profiles.getSupportedProfiles();
+ }
+
+ /**
* Get and check the available tone map modes.
*
* @return the available tone map modes
@@ -1828,7 +1846,7 @@
modeList.contains(CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF));
checkArrayValuesInRange(key, modes,
CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF,
- CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON);
+ CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION);
return modes;
}
diff --git a/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java b/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java
index 5b4b485..df71087 100644
--- a/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java
+++ b/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java
@@ -17,12 +17,14 @@
package android.hardware.cts.helpers;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.devicestate.DeviceStateManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
@@ -30,8 +32,11 @@
import androidx.test.InstrumentationRegistry;
+import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
@@ -273,4 +278,30 @@
}
}
+ /**
+ * Uses {@link DeviceStateManager} to determine if the device is foldable or not. It relies on
+ * the OEM exposing supported states, and setting
+ * com.android.internal.R.array.config_foldedDeviceStates correctly with the folded states.
+ *
+ * @return true is the device is a foldable; false otherwise
+ */
+ public static boolean isDeviceFoldable(Context mContext) {
+ DeviceStateManager deviceStateManager =
+ mContext.getSystemService(DeviceStateManager.class);
+ if (deviceStateManager == null) {
+ Log.w(TAG, "Couldn't locate DeviceStateManager to detect if the device is foldable"
+ + " or not. Defaulting to not-foldable.");
+ return false;
+ }
+ Set<Integer> supportedStates = Arrays.stream(
+ deviceStateManager.getSupportedStates()).boxed().collect(Collectors.toSet());
+
+ Resources systemRes = Resources.getSystem();
+ int foldedStatesArrayIdentifier = systemRes.getIdentifier("config_foldedDeviceStates",
+ "array", "android");
+ int[] foldedDeviceStates = systemRes.getIntArray(foldedStatesArrayIdentifier);
+
+ // Device is a foldable if supportedStates contains any state in foldedDeviceStates
+ return Arrays.stream(foldedDeviceStates).anyMatch(supportedStates::contains);
+ }
}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
index 18d11a3..93e43c9 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
@@ -309,30 +309,22 @@
/**
* This method is used to remove unexpected events if the test should only
* contain session level events.
- * In special case, there are some events, such as {@link #TYPE_VIEW_INSETS_CHANGED},
- * will be accidentally generated by the system. Because these events were
- * not expected in the test, remove them if needed.
+ * In special case, there are some events, such as {@link #TYPE_WINDOW_BOUNDS_CHANGED}
+ * and {@link #TYPE_VIEW_INSETS_CHANGED}, will be accidentally generated by
+ * the system. Because these events were not expected in the test, remove
+ * them if needed.
*/
public static List<ContentCaptureEvent> removeUnexpectedEvents(
@NonNull List<ContentCaptureEvent> events) {
return Collections.unmodifiableList(events).stream().filter(
- e -> e.getType() < TYPE_VIEW_INSETS_CHANGED
+ e -> e.getType() != TYPE_WINDOW_BOUNDS_CHANGED
+ && e.getType() != TYPE_VIEW_INSETS_CHANGED
&& e.getType() != TYPE_VIEW_TREE_APPEARING
&& e.getType() != TYPE_VIEW_TREE_APPEARED
).collect(Collectors.toList());
}
/**
- * Used to remove the unknown event
- */
- public static List<ContentCaptureEvent> removeUnknownEvent(
- @NonNull List<ContentCaptureEvent> events) {
- return Collections.unmodifiableList(events).stream().filter(
- e -> e.getType() <= TYPE_VIEW_INSETS_CHANGED
- ).collect(Collectors.toList());
- }
-
- /**
* Asserts that a session for the given activity has events at all.
*/
public static void assertNoEvents(@NonNull Session session,
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
index 7e454cf..7685053 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
@@ -39,6 +39,7 @@
import android.view.contentcapture.DataShareRequest;
import android.view.contentcapture.ViewNode;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -72,6 +73,9 @@
private static int sIdCounter;
+ private static Object sLock = new Object();
+
+ @GuardedBy("sLock")
private static ServiceWatcher sServiceWatcher;
private final int mId = ++sIdCounter;
@@ -158,11 +162,13 @@
@NonNull
public static ServiceWatcher setServiceWatcher() {
- if (sServiceWatcher != null) {
- throw new IllegalStateException("There Can Be Only One!");
+ synchronized (sLock) {
+ if (sServiceWatcher != null) {
+ throw new IllegalStateException("There Can Be Only One!");
+ }
+ sServiceWatcher = new ServiceWatcher();
+ return sServiceWatcher;
}
- sServiceWatcher = new ServiceWatcher();
- return sServiceWatcher;
}
public static void resetStaticState() {
@@ -173,20 +179,29 @@
// TODO(b/123540602): each test should use a different service instance, but we need
// to provide onConnected() / onDisconnected() methods first and then change the infra so
// we can wait for those
+ synchronized (sLock) {
+ if (sServiceWatcher != null) {
+ Log.wtf(TAG, "resetStaticState(): should not have sServiceWatcher");
+ sServiceWatcher = null;
+ }
+ }
+ }
- if (sServiceWatcher != null) {
- Log.wtf(TAG, "resetStaticState(): should not have sServiceWatcher");
- sServiceWatcher = null;
+ private static ServiceWatcher getServiceWatcher() {
+ synchronized (sLock) {
+ return sServiceWatcher;
}
}
public static void clearServiceWatcher() {
- if (sServiceWatcher != null) {
- if (sServiceWatcher.mReadyToClear) {
- sServiceWatcher.mService = null;
- sServiceWatcher = null;
- } else {
- sServiceWatcher.mReadyToClear = true;
+ synchronized (sLock) {
+ if (sServiceWatcher != null) {
+ if (sServiceWatcher.mReadyToClear) {
+ sServiceWatcher.mService = null;
+ sServiceWatcher = null;
+ } else {
+ sServiceWatcher.mReadyToClear = true;
+ }
}
}
}
@@ -204,21 +219,22 @@
@Override
public void onConnected() {
- Log.i(TAG, "onConnected(id=" + mId + "): sServiceWatcher=" + sServiceWatcher);
+ final ServiceWatcher sw = getServiceWatcher();
+ Log.i(TAG, "onConnected(id=" + mId + "): sServiceWatcher=" + sw);
- if (sServiceWatcher == null) {
+ if (sw == null) {
addException("onConnected() without a watcher");
return;
}
- if (!sServiceWatcher.mReadyToClear && sServiceWatcher.mService != null) {
- addException("onConnected(): already created: %s", sServiceWatcher);
+ if (!sw.mReadyToClear && sw.mService != null) {
+ addException("onConnected(): already created: %s", sw);
return;
}
- sServiceWatcher.mService = this;
- sServiceWatcher.mCreated.countDown();
- sServiceWatcher.mReadyToClear = false;
+ sw.mService = this;
+ sw.mCreated.countDown();
+ sw.mReadyToClear = false;
if (mConnectedLatch.getCount() == 0) {
addException("already connected: %s", mConnectedLatch);
@@ -228,19 +244,20 @@
@Override
public void onDisconnected() {
- Log.i(TAG, "onDisconnected(id=" + mId + "): sServiceWatcher=" + sServiceWatcher);
+ final ServiceWatcher sw = getServiceWatcher();
+ Log.i(TAG, "onDisconnected(id=" + mId + "): sServiceWatcher=" + sw);
if (mDisconnectedLatch.getCount() == 0) {
addException("already disconnected: %s", mConnectedLatch);
}
mDisconnectedLatch.countDown();
- if (sServiceWatcher == null) {
+ if (sw == null) {
addException("onDisconnected() without a watcher");
return;
}
- if (sServiceWatcher.mService == null) {
- addException("onDisconnected(): no service on %s", sServiceWatcher);
+ if (sw.mService == null) {
+ addException("onDisconnected(): no service on %s", sw);
return;
}
// Notify test case as well
@@ -249,7 +266,7 @@
mOnDisconnectListener = null;
latch.countDown();
}
- sServiceWatcher.mDestroyed.countDown();
+ sw.mDestroyed.countDown();
clearServiceWatcher();
}
@@ -449,7 +466,7 @@
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
super.dump(fd, pw, args);
- pw.print("sServiceWatcher: "); pw.println(sServiceWatcher);
+ pw.print("sServiceWatcher: "); pw.println(getServiceWatcher());
pw.print("sExceptions: "); pw.println(sExceptions);
pw.print("sIdCounter: "); pw.println(sIdCounter);
pw.print("mId: "); pw.println(mId);
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingService.java
index d1f0c6a..3a1ada3 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingService.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingService.java
@@ -24,6 +24,7 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.DataShareRequest;
import android.view.contentcapture.DataShareWriteAdapter;
@@ -73,6 +74,8 @@
public void onWrite(ParcelFileDescriptor destination) {
if (mShouldAttemptConcurrentRequest) {
attemptConcurrentRequest();
+ // Waiting for the request to arrive at the server
+ SystemClock.sleep(500);
}
try (OutputStream outputStream =
new ParcelFileDescriptor.AutoCloseOutputStream(destination)) {
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingServiceTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingServiceTest.java
index 421a5bd..0e71cac 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingServiceTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingServiceTest.java
@@ -141,9 +141,9 @@
CtsContentCaptureService ccService = enableService();
ccService.setDataSharingEnabled(true);
- sKillingStage = KillingStage.BEFORE_WRITE;
- getApplicationContext().startService(
- new Intent(getApplicationContext(), OutOfProcessDataSharingService.class));
+ Intent intent = new Intent(getApplicationContext(), OutOfProcessDataSharingService.class);
+ intent.putExtra("KillingStage", KillingStage.BEFORE_WRITE.name());
+ getApplicationContext().startService(intent);
PollingCheck.waitFor(() -> ccService.mDataShareSessionErrorCode > 0);
@@ -156,13 +156,12 @@
CtsContentCaptureService ccService = enableService();
ccService.setDataSharingEnabled(true);
- sKillingStage = KillingStage.DURING_WRITE;
- getApplicationContext().startService(
- new Intent(getApplicationContext(), OutOfProcessDataSharingService.class));
+ Intent intent = new Intent(getApplicationContext(), OutOfProcessDataSharingService.class);
+ intent.putExtra("KillingStage", KillingStage.DURING_WRITE.name());
+ getApplicationContext().startService(intent);
- PollingCheck.waitFor(() -> ccService.mDataShareSessionErrorCode > 0);
+ PollingCheck.waitFor(() -> ccService.mDataShareSessionFinished);
- assertThat(ccService.mDataShareSessionErrorCode).isEqualTo(
- ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN);
+ assertThat(ccService.mDataShareSessionSucceeded).isTrue();
}
}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/EventsAssertor.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/EventsAssertor.java
index d00b031..6f6d457 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/EventsAssertor.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/EventsAssertor.java
@@ -102,6 +102,16 @@
}
/**
+ * Asserts the basic contents of a {@link ContentCaptureEvent#TYPE_CONTEXT_UPDATED} event.
+ */
+ public EventsAssertor assertContextUpdated() {
+ assertNextEvent((event) -> assertSessionLevelEvent(event),
+ ContentCaptureEvent.TYPE_CONTEXT_UPDATED,
+ "no TYPE_CONTEXT_UPDATED event");
+ return this;
+ }
+
+ /**
* Asserts the contents of a {@link ContentCaptureEvent#TYPE_VIEW_APPEARED}
* event for a decor view.
*
@@ -223,6 +233,27 @@
return assertViewDisappeared(session.newAutofillId(parentId, childId));
}
+ /**
+ * Asserts the contents of a {@link ContentCaptureEvent#TYPE_VIEW_TEXT_CHANGED} event.
+ */
+ @NonNull
+ public EventsAssertor assertViewTextChanged(AutofillId expectedId, String expectedText) {
+ assertNextEvent((event) -> assertTextChangedEvent(event, expectedId, expectedText),
+ ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED,
+ String.format("no TYPE_VIEW_TEXT_CHANGED event for %s:%s",
+ expectedId, expectedText));
+ return this;
+ }
+
+ private String assertTextChangedEvent(ContentCaptureEvent event, AutofillId expectedId,
+ String expectedText) {
+ assertWithMessage("Wrong id on %s", event).that(event.getId())
+ .isEqualTo(expectedId);
+ assertWithMessage("Wrong text on %s", event).that(event.getText().toString())
+ .isEqualTo(expectedText);
+ return null;
+ }
+
@Nullable
private String assertVirtualViewEvent(@NonNull ContentCaptureEvent event,
@NonNull AutofillId expectedId, @Nullable String expectedText) {
@@ -260,7 +291,7 @@
assertWithMessage("no autofillIds on event %s", event).that(ids)
.isNotNull();
assertWithMessage("wrong autofillId on event %s", event)
- .that(ids).containsExactly((Object[]) expectedIds).inOrder();
+ .that(ids).containsExactly((Object[]) expectedIds);
assertWithMessage("event %s should not have autofillId", event)
.that(event.getId()).isNull();
return null;
@@ -383,6 +414,10 @@
+ mEvents.size() + "): " + mEvents);
}
+ public ContentCaptureEvent getLastEvent() {
+ return mEvents.get(mNextEvent - 1);
+ }
+
private interface EventAssertion {
@Nullable
String getErrorMessage(@NonNull ContentCaptureEvent event);
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java
index 0857828..8b86214 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java
@@ -23,7 +23,6 @@
import static android.contentcaptureservice.cts.Assertions.assertViewTreeFinished;
import static android.contentcaptureservice.cts.Assertions.assertViewTreeStarted;
import static android.contentcaptureservice.cts.Assertions.assertViewsOptionallyDisappeared;
-import static android.contentcaptureservice.cts.Assertions.removeUnknownEvent;
import static com.google.common.truth.Truth.assertThat;
@@ -97,17 +96,11 @@
@NonNull
public List<ContentCaptureEvent> assertJustInitialViewsAppeared(@NonNull Session session,
int additionalEvents) {
+ assertBaseInformation(session);
+
final LoginActivity activity = this;
final ContentCaptureSessionId sessionId = session.id;
- assertRightActivity(session, sessionId, activity);
-
- // Sanity check
- assertSessionId(sessionId, activity.mUsernameLabel);
- assertSessionId(sessionId, activity.mUsername);
- assertSessionId(sessionId, activity.mPassword);
- assertSessionId(sessionId, activity.mPasswordLabel);
-
- final List<ContentCaptureEvent> events = removeUnknownEvent(session.getEvents());
+ final List<ContentCaptureEvent> events = session.getEvents();
Log.v(TAG, "events(" + events.size() + "): " + events);
// TODO(b/123540067): ideally it should be X so it reflects just the views defined
// in the layout - right now it's generating events for 2 intermediate parents
@@ -139,6 +132,52 @@
}
/**
+ * Asserts the events generated when this activity was launched, up to the
+ * {@code TYPE_INITIAL_VIEW_HIERARCHY_FINISHED} event.
+ */
+ @NonNull
+ public EventsAssertor assertInitialViewsAppeared(@NonNull Session session) {
+ assertBaseInformation(session);
+
+ final LoginActivity activity = this;
+ final ContentCaptureSessionId sessionId = session.id;
+ final List<ContentCaptureEvent> events = session.getEvents();
+ Log.v(TAG, "events(" + events.size() + "): " + events);
+ final AutofillId rootId = activity.getRootView().getAutofillId();
+ final View grandpa1 = activity.getGrandParent();
+ final View grandpa2 = activity.getGrandGrandParent();
+ final View decorView = activity.getDecorView();
+ final View rootView = activity.getRootView();
+
+ EventsAssertor assertor = new EventsAssertor(events);
+ assertor.isAtLeast(MIN_EVENTS)
+ .assertSessionResumed()
+ .assertViewTreeStarted()
+ .assertDecorViewAppeared(decorView)
+ .assertViewAppeared(grandpa2, decorView.getAutofillId())
+ .assertViewAppeared(grandpa1, grandpa2.getAutofillId())
+ .assertViewAppeared(sessionId, rootView, grandpa1.getAutofillId())
+ .assertViewAppeared(sessionId, activity.mUsernameLabel, rootId)
+ .assertViewAppeared(sessionId, activity.mUsername, rootId)
+ .assertViewAppeared(sessionId, activity.mPasswordLabel, rootId)
+ .assertViewAppeared(sessionId, activity.mPassword, rootId)
+ .assertViewTreeFinished();
+
+ return assertor;
+ }
+
+ private void assertBaseInformation(@NonNull Session session) {
+ final LoginActivity activity = this;
+ final ContentCaptureSessionId sessionId = session.id;
+ assertRightActivity(session, sessionId, activity);
+
+ assertSessionId(sessionId, activity.mUsernameLabel);
+ assertSessionId(sessionId, activity.mUsername);
+ assertSessionId(sessionId, activity.mPassword);
+ assertSessionId(sessionId, activity.mPasswordLabel);
+ }
+
+ /**
* Asserts the initial views disappeared after the activity was finished.
*/
public void assertInitialViewsDisappeared(@NonNull List<ContentCaptureEvent> events,
@@ -180,6 +219,25 @@
assertViewTreeFinished(events, i + 2);
}
+ /**
+ * Asserts the initial views disappeared after the activity was finished.
+ */
+ public void assertInitialViewsDisappeared(@NonNull EventsAssertor assertor) {
+ final LoginActivity activity = this;
+ final AutofillId rootId = activity.getRootView().getAutofillId();
+ final View decorView = activity.getDecorView();
+ final View grandpa1 = activity.getGrandParent();
+ final View grandpa2 = activity.getGrandGrandParent();
+
+ assertor.assertViewTreeStarted()
+ .assertViewDisappeared(
+ rootId, grandpa1.getAutofillId(), grandpa2.getAutofillId(),
+ decorView.getAutofillId(),
+ activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
+ activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId())
+ .assertViewTreeFinished();
+ }
+
@Override
protected void onResume() {
super.onResume();
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
index acd5dc8..5a67ac0 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
@@ -26,7 +26,6 @@
import static android.contentcaptureservice.cts.Assertions.assertSessionPaused;
import static android.contentcaptureservice.cts.Assertions.assertSessionResumed;
import static android.contentcaptureservice.cts.Assertions.assertViewAppeared;
-import static android.contentcaptureservice.cts.Assertions.assertViewTextChanged;
import static android.contentcaptureservice.cts.Assertions.assertViewTreeFinished;
import static android.contentcaptureservice.cts.Assertions.assertViewTreeStarted;
import static android.contentcaptureservice.cts.Assertions.assertViewsOptionallyDisappeared;
@@ -314,16 +313,18 @@
final Session session = service.getOnlyFinishedSession();
Log.v(TAG, "session id: " + session.id);
- final int additionalEvents = 2;
- final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
- additionalEvents);
+ final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
- final ContentCaptureEvent event1 = assertContextUpdated(events, LoginActivity.MIN_EVENTS);
+ assertor.isAtLeast(LoginActivity.MIN_EVENTS + 2)
+ .assertContextUpdated();
+
+ final ContentCaptureEvent event1 = assertor.getLastEvent();
final ContentCaptureContext actualContext = event1.getContentCaptureContext();
assertContentCaptureContext(actualContext);
- final ContentCaptureEvent event2 = assertContextUpdated(events,
- LoginActivity.MIN_EVENTS + 1);
+ assertor.assertContextUpdated();
+
+ final ContentCaptureEvent event2 = assertor.getLastEvent();
assertThat(event2.getContentCaptureContext()).isNull();
}
@@ -407,20 +408,14 @@
watcher.waitFor(DESTROYED);
final Session session = service.getOnlyFinishedSession();
- final ContentCaptureSessionId sessionId = session.id;
- assertRightActivity(session, sessionId, activity);
+ final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
- final int additionalEvents = 2;
- final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
- additionalEvents);
+ assertor.isAtLeast(LoginActivity.MIN_EVENTS + 2)
+ .assertViewTextChanged(activity.mUsername.getAutofillId(), "USER")
+ .assertViewTextChanged(activity.mPassword.getAutofillId(), "PASS");
- final int i = LoginActivity.MIN_EVENTS;
-
- assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "USER");
- assertViewTextChanged(events, i + 1, activity.mPassword.getAutofillId(), "PASS");
-
- activity.assertInitialViewsDisappeared(events, additionalEvents);
+ activity.assertInitialViewsDisappeared(assertor);
}
@Test
@@ -454,29 +449,26 @@
watcher.waitFor(DESTROYED);
final Session session = service.getOnlyFinishedSession();
- final ContentCaptureSessionId sessionId = session.id;
- assertRightActivity(session, sessionId, activity);
+ final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
- final int additionalEvents = 8;
- final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
- additionalEvents);
+ final AutofillId usernameId = activity.mUsername.getAutofillId();
+ final AutofillId passwordId = activity.mPassword.getAutofillId();
- final int i = LoginActivity.MIN_EVENTS;
+ assertor.isAtLeast(LoginActivity.MIN_EVENTS + 8)
+ .assertViewTextChanged(activity.mUsername.getAutofillId(), "a")
+ .assertViewTextChanged(usernameId, "ab")
+ .assertViewTextChanged(usernameId, "")
+ .assertViewTextChanged(usernameId, "abc")
+ .assertViewTextChanged(passwordId, "d")
+ .assertViewTextChanged(passwordId, "")
+ .assertViewTextChanged(passwordId, "")
+ .assertViewTextChanged(passwordId, "de")
+ .assertViewTextChanged(passwordId, "def")
+ .assertViewTextChanged(passwordId, "")
+ .assertViewTextChanged(usernameId, "abc");
- assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "a");
- assertViewTextChanged(events, i + 1, activity.mUsername.getAutofillId(), "ab");
- assertViewTextChanged(events, i + 2, activity.mUsername.getAutofillId(), "");
- assertViewTextChanged(events, i + 3, activity.mUsername.getAutofillId(), "abc");
- assertViewTextChanged(events, i + 4, activity.mPassword.getAutofillId(), "d");
- assertViewTextChanged(events, i + 5, activity.mPassword.getAutofillId(), "");
- assertViewTextChanged(events, i + 6, activity.mPassword.getAutofillId(), "");
- assertViewTextChanged(events, i + 7, activity.mPassword.getAutofillId(), "de");
- assertViewTextChanged(events, i + 8, activity.mPassword.getAutofillId(), "def");
- assertViewTextChanged(events, i + 9, activity.mPassword.getAutofillId(), "");
- assertViewTextChanged(events, i + 10, activity.mUsername.getAutofillId(), "abc");
-
- activity.assertInitialViewsDisappeared(events, additionalEvents);
+ activity.assertInitialViewsDisappeared(assertor);
}
@Test
@@ -505,19 +497,13 @@
watcher.waitFor(DESTROYED);
final Session session = service.getOnlyFinishedSession();
- final ContentCaptureSessionId sessionId = session.id;
- assertRightActivity(session, sessionId, activity);
+ final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
- final int additionalEvents = 5;
- final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
- additionalEvents);
+ assertor.isAtLeast(LoginActivity.MIN_EVENTS + 5)
+ .assertViewTextChanged(activity.mUsername.getAutofillId(), "Android");
- final int i = LoginActivity.MIN_EVENTS;
-
- assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "Android");
-
- activity.assertInitialViewsDisappeared(events, additionalEvents);
+ activity.assertInitialViewsDisappeared(assertor);
}
@Test
@@ -550,26 +536,22 @@
watcher.waitFor(DESTROYED);
final Session session = service.getOnlyFinishedSession();
- final ContentCaptureSessionId sessionId = session.id;
- assertRightActivity(session, sessionId, activity);
+ final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
- final int additionalEvents = 4;
- final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
- additionalEvents);
+ assertor.isAtLeast(LoginActivity.MIN_EVENTS + 4)
+ .assertViewTextChanged(activity.mUsername.getAutofillId(), "Good");
+ assertComposingSpan(assertor.getLastEvent().getText(), 0, 4);
- final int i = LoginActivity.MIN_EVENTS;
+ assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "Good ");
+ assertNoComposingSpan(assertor.getLastEvent().getText());
- assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "Good");
- assertComposingSpan(events.get(i).getText(), 0, 4);
- assertViewTextChanged(events, i + 1, activity.mUsername.getAutofillId(), "Good ");
- assertNoComposingSpan(events.get(i + 1).getText());
- assertViewTextChanged(events, i + 2, activity.mUsername.getAutofillId(), "Good morning");
+ assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "Good morning");
// TODO: Change how the appending works to more realistically test the case where only
// "morning" is in the composing state.
- assertComposingSpan(events.get(i + 2).getText(), 0, 12);
+ assertComposingSpan(assertor.getLastEvent().getText(), 0, 12);
- activity.assertInitialViewsDisappeared(events, additionalEvents);
+ activity.assertInitialViewsDisappeared(assertor);
}
@Test
@@ -597,20 +579,14 @@
watcher.waitFor(DESTROYED);
final Session session = service.getOnlyFinishedSession();
- final ContentCaptureSessionId sessionId = session.id;
- assertRightActivity(session, sessionId, activity);
+ final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
- final int additionalEvents = 3;
- final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
- additionalEvents);
+ assertor.isAtLeast(LoginActivity.MIN_EVENTS + 3)
+ .assertViewTextChanged(activity.mUsername.getAutofillId(), "Good morning")
+ .assertViewTextChanged(activity.mPassword.getAutofillId(), "How are you");
- final int i = LoginActivity.MIN_EVENTS;
-
- assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "Good morning");
- assertViewTextChanged(events, i + 1, activity.mPassword.getAutofillId(), "How are you");
-
- activity.assertInitialViewsDisappeared(events, additionalEvents);
+ activity.assertInitialViewsDisappeared(assertor);
}
@Test
@@ -642,27 +618,24 @@
watcher.waitFor(DESTROYED);
final Session session = service.getOnlyFinishedSession();
- final ContentCaptureSessionId sessionId = session.id;
- assertRightActivity(session, sessionId, activity);
+ final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
- final int additionalEvents = 5;
- final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
- additionalEvents);
+ assertor.isAtLeast(LoginActivity.MIN_EVENTS + 5)
+ // TODO: The first two events should probably be merged.
+ .assertViewTextChanged(activity.mUsername.getAutofillId(), "Android");
+ assertNoComposingSpan(assertor.getLastEvent().getText());
- final int i = LoginActivity.MIN_EVENTS;
+ assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "Android");
+ assertComposingSpan(assertor.getLastEvent().getText(), 1, 3);
- // TODO: The first two events should probably be merged.
- assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "Android");
- assertNoComposingSpan(events.get(i).getText());
- assertViewTextChanged(events, i + 1, activity.mUsername.getAutofillId(), "Android");
- assertComposingSpan(events.get(i + 1).getText(), 1, 3);
- assertViewTextChanged(events, i + 2, activity.mUsername.getAutofillId(), "Android");
- assertNoComposingSpan(events.get(i + 2).getText());
- assertViewTextChanged(events, i + 3, activity.mUsername.getAutofillId(), "end");
- assertNoComposingSpan(events.get(i + 3).getText());
+ assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "Android");
+ assertNoComposingSpan(assertor.getLastEvent().getText());
- activity.assertInitialViewsDisappeared(events, additionalEvents);
+ assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "end");
+ assertNoComposingSpan(assertor.getLastEvent().getText());
+
+ activity.assertInitialViewsDisappeared(assertor);
}
private void appendText(EditText editText, String text) {
@@ -920,20 +893,16 @@
watcher.waitFor(DESTROYED);
final Session session = service.getOnlyFinishedSession();
- Log.v(TAG, "session id: " + session.id);
-
final ContentCaptureSessionId sessionId = session.id;
- assertRightActivity(session, sessionId, activity);
-
- final List<ContentCaptureEvent> events = activity.assertJustInitialViewsAppeared(session,
- /* additionalEvents= */ 2);
+ Log.v(TAG, "session id: " + sessionId);
final AutofillId rootId = activity.getRootView().getAutofillId();
- int i = LoginActivity.MIN_EVENTS - 1;
- assertViewTreeFinished(events, i);
- assertViewTreeStarted(events, i + 1);
- assertViewAppeared(events, i + 2, sessionId, child, rootId);
- assertViewTreeFinished(events, i + 3);
+ final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
+
+ assertor.isAtLeast(LoginActivity.MIN_EVENTS + 3)
+ .assertViewTreeStarted()
+ .assertViewAppeared(sessionId, child, rootId)
+ .assertViewTreeFinished();
}
@Test
@@ -963,49 +932,26 @@
final Session session = service.getOnlyFinishedSession();
Log.v(TAG, "session id: " + session.id);
-
final ContentCaptureSessionId sessionId = session.id;
- assertRightActivity(session, sessionId, activity);
- final int additionalEvents = 2; // 2 children views
- final List<ContentCaptureEvent> events = activity.assertJustInitialViewsAppeared(session,
- additionalEvents);
- assertThat(events.size()).isAtLeast(LoginActivity.MIN_EVENTS + 5);
final View decorView = activity.getDecorView();
final View grandpa1 = activity.getGrandParent();
final View grandpa2 = activity.getGrandGrandParent();
final AutofillId rootId = activity.getRootView().getAutofillId();
- int i = LoginActivity.MIN_EVENTS - 1;
- assertViewTreeFinished(events, i);
- assertViewTreeStarted(events, i + 1);
- assertViewAppeared(events, i + 2, sessionId, children[0], rootId);
- assertViewAppeared(events, i + 3, sessionId, children[1], rootId);
- assertViewTreeFinished(events, i + 4);
+ final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
- // TODO(b/122315042): assert parents disappeared
- if (true) return;
-
- // TODO(b/122315042): sometimes we get decor view disappareared events, sometimes we don't
- // As we don't really care about those, let's fix it!
- try {
- assertViewsOptionallyDisappeared(events, LoginActivity.MIN_EVENTS + additionalEvents,
- rootId,
- grandpa1.getAutofillId(), grandpa2.getAutofillId(),
- activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
- activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId(),
- children[0].getAutofillId(), children[1].getAutofillId());
- } catch (AssertionError e) {
- Log.e(TAG, "Hack-ignoring assertion without decor view: " + e);
- // Try again removing it...
- assertViewsOptionallyDisappeared(events, LoginActivity.MIN_EVENTS + additionalEvents,
- rootId,
- grandpa1.getAutofillId(), grandpa2.getAutofillId(),
- decorView.getAutofillId(),
- activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
- activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId(),
- children[0].getAutofillId(), children[1].getAutofillId());
-
- }
+ assertor.isAtLeast(LoginActivity.MIN_EVENTS + 5)
+ .assertViewTreeStarted()
+ .assertViewAppeared(sessionId, children[0], rootId)
+ .assertViewAppeared(sessionId, children[1], rootId)
+ .assertViewTreeFinished()
+ .assertViewDisappeared(
+ decorView.getAutofillId(),
+ grandpa1.getAutofillId(), grandpa2.getAutofillId(),
+ rootId,
+ activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
+ activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId(),
+ children[0].getAutofillId(), children[1].getAutofillId());
}
@Test
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
index 27224c8..86a8b02 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
@@ -24,6 +24,7 @@
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
+import android.text.TextUtils;
import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.DataShareRequest;
import android.view.contentcapture.DataShareWriteAdapter;
@@ -47,6 +48,7 @@
byte[] mDataWritten = new byte[10_000];
String mLocusId = "DataShare_CTSTest";
String mMimeType = "application/octet-stream";
+ DataSharingServiceTest.KillingStage mKillingStage = DataSharingServiceTest.KillingStage.NONE;
private final IBinder mBinder = new IOutOfProcessDataSharingService.Stub() {
@Override
@@ -59,6 +61,7 @@
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
+ setupKillingStage(intent);
shareData();
return START_NOT_STICKY;
}
@@ -69,6 +72,14 @@
return mBinder;
}
+ private void setupKillingStage(Intent intent) {
+ String killingStage = intent.getStringExtra("KillingStage");
+ if (TextUtils.isEmpty(killingStage)) {
+ return;
+ }
+ mKillingStage = DataSharingServiceTest.KillingStage.valueOf(killingStage);
+ }
+
private void shareData() {
ContentCaptureManager manager =
getApplicationContext().getSystemService(ContentCaptureManager.class);
@@ -83,14 +94,14 @@
public void onWrite(ParcelFileDescriptor destination) {
try (OutputStream outputStream =
new ParcelFileDescriptor.AutoCloseOutputStream(destination)) {
- if (DataSharingServiceTest.sKillingStage
+ if (mKillingStage
== DataSharingServiceTest.KillingStage.BEFORE_WRITE) {
Process.killProcess(Process.myPid());
return;
}
sRandom.nextBytes(mDataWritten);
outputStream.write(mDataWritten);
- if (DataSharingServiceTest.sKillingStage
+ if (mKillingStage
== DataSharingServiceTest.KillingStage.DURING_WRITE) {
Process.killProcess(Process.myPid());
return;
diff --git a/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java b/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java
index f0f22ae..ab586b2 100644
--- a/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java
+++ b/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java
@@ -81,7 +81,7 @@
.setCustomColor(mColorStateList)
.build();
- assertControl(control, true);
+ assertControl(control, true /* isStateless */, true /* authRequired */);
}
@Test
@@ -98,13 +98,14 @@
.setStatusText(STATUS_TEXT_STATEFUL)
.setCustomIcon(mIcon)
.setCustomColor(mColorStateList)
+ .setAuthRequired(false)
.build();
Control updatedControl = new Control.StatefulBuilder(control).build();
- assertControl(updatedControl, false);
+ assertControl(updatedControl, false /* isStateless */, false /* authRequired */);
}
- private void assertControl(Control control, boolean isStateless) {
+ private void assertControl(Control control, boolean isStateless, boolean authRequired) {
assertEquals(control.getTitle(), TITLE);
assertEquals(control.getSubtitle(), SUBTITLE);
assertEquals(control.getStructure(), STRUCTURE);
@@ -116,5 +117,6 @@
assertEquals(control.getControlId(), CONTROL_ID2);
assertEquals(control.getCustomColor(), mColorStateList);
assertEquals(control.getCustomIcon(), mIcon);
+ assertEquals(control.isAuthRequired(), authRequired);
}
}
diff --git a/tests/controls/src/android/controls/cts/CtsControlsService.java b/tests/controls/src/android/controls/cts/CtsControlsService.java
index 301c34e..36cfdd8 100644
--- a/tests/controls/src/android/controls/cts/CtsControlsService.java
+++ b/tests/controls/src/android/controls/cts/CtsControlsService.java
@@ -96,6 +96,7 @@
.setDeviceType(DeviceTypes.TYPE_LIGHT)
.setStructure("Home")
.setControlTemplate(template)
+ .setAuthRequired(false)
.build();
}
@@ -351,6 +352,7 @@
.setCustomIcon(c.getCustomIcon())
.setCustomColor(c.getCustomColor())
.setStatus(c.getStatus())
- .setStatusText(c.getStatusText());
+ .setStatusText(c.getStatusText())
+ .setAuthRequired(c.isAuthRequired());
}
}
diff --git a/tests/controls/src/android/controls/cts/CtsControlsServiceTest.java b/tests/controls/src/android/controls/cts/CtsControlsServiceTest.java
index 61ccef3..7d5fb2a 100644
--- a/tests/controls/src/android/controls/cts/CtsControlsServiceTest.java
+++ b/tests/controls/src/android/controls/cts/CtsControlsServiceTest.java
@@ -371,6 +371,7 @@
assertEquals(c1.getControlId(), c2.getControlId());
assertEquals(c1.getCustomIcon(), c2.getCustomIcon());
assertEquals(c1.getCustomColor(), c2.getCustomColor());
+ assertEquals(c1.isAuthRequired(), c2.isAuthRequired());
assertTemplateEquals(c1.getControlTemplate(), c2.getControlTemplate());
}
diff --git a/tests/devicepolicy/OWNERS b/tests/devicepolicy/OWNERS
index cf88726..19b6194 100644
--- a/tests/devicepolicy/OWNERS
+++ b/tests/devicepolicy/OWNERS
@@ -1,2 +1,2 @@
# Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204
-file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
\ No newline at end of file
+file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
diff --git a/tests/devicepolicy/res/drawable/test_drawable_1.xml b/tests/devicepolicy/res/drawable/test_drawable_1.xml
new file mode 100644
index 0000000..8a20885
--- /dev/null
+++ b/tests/devicepolicy/res/drawable/test_drawable_1.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FFF44336"
+ android:pathData="M11,15h2v2h-2v-2zM11,7h2v6h-2L11,7zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
+</vector>
diff --git a/tests/devicepolicy/res/drawable/test_drawable_2.xml b/tests/devicepolicy/res/drawable/test_drawable_2.xml
new file mode 100644
index 0000000..98569a3
--- /dev/null
+++ b/tests/devicepolicy/res/drawable/test_drawable_2.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,15h2v2h-2v-2zM11,7h2v6h-2L11,7zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
+</vector>
diff --git a/tests/devicepolicy/res/values/strings.xml b/tests/devicepolicy/res/values/strings.xml
new file mode 100644
index 0000000..47ddabc
--- /dev/null
+++ b/tests/devicepolicy/res/values/strings.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <string name="test_string">test string</string>
+</resources>
\ No newline at end of file
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/AccountManagementTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/AccountManagementTest.java
index 9c5938e..1b1eb9c 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/AccountManagementTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/AccountManagementTest.java
@@ -16,6 +16,8 @@
package android.devicepolicy.cts;
+import static android.os.UserManager.DISALLOW_MODIFY_ACCOUNTS;
+
import static com.android.queryable.queries.IntentFilterQuery.intentFilter;
import static com.android.queryable.queries.ServiceQuery.service;
@@ -25,7 +27,6 @@
import android.accounts.Account;
import android.accounts.AccountManager;
-import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.admin.RemoteDevicePolicyManager;
import android.content.ComponentName;
@@ -49,13 +50,10 @@
import org.junit.Before;
import org.junit.ClassRule;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.IOException;
-
@RunWith(BedsteadJUnit4.class)
public final class AccountManagementTest {
@ClassRule
@@ -158,78 +156,64 @@
assertThat(mDpm.getAccountTypesWithManagementDisabled()).isEmpty();
}
- @Ignore("b/197491427")
@Test
@Postsubmit(reason = "new test")
@CanSetPolicyTest(policy = AccountManagement.class)
public void addAccount_fromDpcWithAccountManagementDisabled_accountAdded()
- throws OperationCanceledException, AuthenticatorException, IOException {
+ throws Exception {
try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
mDpm.setAccountManagementDisabled(mAdmin, EXISTING_ACCOUNT_TYPE, /* disabled= */ true);
// Management is disabled, but the DO/PO is still allowed to use the APIs
- // TODO(b/197491427): AccountManager support in TestApp
- // Do the following steps on the TestApp side:
- // Bundle result = addAccountWithType(EXISTING_ACCOUNT_TYPE);
+ Bundle result = addAccountWithType(sDeviceState.dpc(), EXISTING_ACCOUNT_TYPE);
- // assertThat(result.getString(AccountManager.KEY_ACCOUNT_TYPE))
- // .isEqualTo(EXISTING_ACCOUNT_TYPE);
+ assertThat(result.getString(AccountManager.KEY_ACCOUNT_TYPE))
+ .isEqualTo(EXISTING_ACCOUNT_TYPE);
} finally {
mDpm.setAccountManagementDisabled(mAdmin, EXISTING_ACCOUNT_TYPE, /* disabled= */ false);
- // TODO(b/197491427): AccountManager support in TestApp
- // removeAccount(ACCOUNT_WITH_EXISTING_TYPE);
}
}
- @Ignore("b/197491427")
@Test
@Postsubmit(reason = "new test")
@CanSetPolicyTest(policy = AccountManagement.class)
public void addAccount_fromDpcWithDisallowModifyAccountsRestriction_accountAdded()
- throws OperationCanceledException, AuthenticatorException, IOException {
+ throws Exception {
try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
- mDpm.addUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
+ mDpm.addUserRestriction(mAdmin, DISALLOW_MODIFY_ACCOUNTS);
// Management is disabled, but the DO/PO is still allowed to use the APIs
- // TODO(b/197491427): AccountManager support in TestApp
- // Do the following steps on the TestApp side:
- // Bundle result = addAccountWithType(EXISTING_ACCOUNT_TYPE);
+ Bundle result = addAccountWithType(sDeviceState.dpc(), EXISTING_ACCOUNT_TYPE);
- //assertThat(result.getString(AccountManager.KEY_ACCOUNT_TYPE))
- // .isEqualTo(EXISTING_ACCOUNT_TYPE);
+ assertThat(result.getString(AccountManager.KEY_ACCOUNT_TYPE))
+ .isEqualTo(EXISTING_ACCOUNT_TYPE);
} finally {
- mDpm.clearUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
- // TODO(b/197491427): AccountManager support in TestApp
- // removeAccount(ACCOUNT_WITH_EXISTING_TYPE);
+ mDpm.clearUserRestriction(mAdmin, DISALLOW_MODIFY_ACCOUNTS);
}
}
- @Ignore("b/197491427")
@Test
@Postsubmit(reason = "new test")
@CanSetPolicyTest(policy = AccountManagement.class)
public void removeAccount_fromDpcWithDisallowModifyAccountsRestriction_accountRemoved()
- throws OperationCanceledException, AuthenticatorException, IOException {
+ throws Exception {
try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
mDpm.addUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
// Management is disabled, but the DO/PO is still allowed to use the APIs
- // TODO(b/197491427): AccountManager support in TestApp
- // Do the following steps on the TestApp side:
- // addAccountWithType(EXISTING_ACCOUNT_TYPE);
- // Bundle result = removeAccount(ACCOUNT_WITH_EXISTING_TYPE);
+ addAccountWithType(sDeviceState.dpc(), EXISTING_ACCOUNT_TYPE);
+ Bundle result = removeAccount(sDeviceState.dpc(), ACCOUNT_WITH_EXISTING_TYPE);
- // assertThat(result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)).isTrue();
+ assertThat(result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)).isTrue();
} finally {
mDpm.clearUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
}
}
@Test
- @Postsubmit(reason = "new test with sleep")
+ @Postsubmit(reason = "new test")
@CanSetPolicyTest(policy = AccountManagement.class)
- public void addAccount_withDisallowModifyAccountsRestriction_throwsException()
- throws OperationCanceledException, AuthenticatorException, IOException {
+ public void addAccount_withDisallowModifyAccountsRestriction_throwsException() {
try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
mDpm.addUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
@@ -241,17 +225,16 @@
}
@Test
- @Postsubmit(reason = "new test with sleep")
+ @Postsubmit(reason = "new test")
@CanSetPolicyTest(policy = AccountManagement.class)
public void removeAccount_withDisallowModifyAccountsRestriction_throwsException()
- throws OperationCanceledException, AuthenticatorException, IOException,
- InterruptedException {
+ throws Exception {
try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
- addAccountWithType(EXISTING_ACCOUNT_TYPE);
+ addAccountFromInstrumentedAppWithType(EXISTING_ACCOUNT_TYPE);
mDpm.addUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
assertThrows(OperationCanceledException.class, () ->
- removeAccount(ACCOUNT_WITH_EXISTING_TYPE));
+ removeAccountFromInstrumentedApp(ACCOUNT_WITH_EXISTING_TYPE));
} finally {
// Account is automatically removed when the test app is removed
mDpm.clearUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
@@ -259,7 +242,7 @@
}
@Test
- @Postsubmit(reason = "new test with sleep")
+ @Postsubmit(reason = "new test")
@CanSetPolicyTest(policy = AccountManagement.class)
public void addAccount_withAccountManagementDisabled_throwsException() {
try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
@@ -273,17 +256,16 @@
}
@Test
- @Postsubmit(reason = "new test with sleep")
+ @Postsubmit(reason = "new test")
@CanSetPolicyTest(policy = AccountManagement.class)
public void removeAccount_withAccountManagementDisabled_throwsException()
- throws OperationCanceledException, AuthenticatorException, IOException,
- InterruptedException {
+ throws Exception {
try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
- addAccountWithType(EXISTING_ACCOUNT_TYPE);
+ addAccountFromInstrumentedAppWithType(EXISTING_ACCOUNT_TYPE);
mDpm.setAccountManagementDisabled(mAdmin, EXISTING_ACCOUNT_TYPE, /* disabled= */ true);
assertThrows(OperationCanceledException.class, () ->
- removeAccount(ACCOUNT_WITH_EXISTING_TYPE));
+ removeAccountFromInstrumentedApp(ACCOUNT_WITH_EXISTING_TYPE));
} finally {
// Account is automatically removed when the test app is removed
mDpm.setAccountManagementDisabled(mAdmin, EXISTING_ACCOUNT_TYPE, /* disabled= */ false);
@@ -293,14 +275,25 @@
/**
* Blocks until an account of {@code type} is added.
*/
- // TODO(b/199077745): Remove sleep once AccountManager race condition is fixed
- private Bundle addAccountWithType(String type) {
+ // TODO(b/199077745): Remove poll once AccountManager race condition is fixed
+ private Bundle addAccountFromInstrumentedAppWithType(String type) {
return Poll.forValue("created account bundle", () -> addAccountWithTypeOnce(type))
.toNotBeNull()
.errorOnFail()
.await();
}
+ /**
+ * Blocks until an account of {@code type} is added.
+ */
+ // TODO(b/199077745): Remove poll once AccountManager race condition is fixed
+ private Bundle addAccountWithType(TestAppInstance testApp, String type) {
+ return Poll.forValue("created account bundle", () -> addAccountWithTypeOnce(testApp, type))
+ .toNotBeNull()
+ .errorOnFail()
+ .await();
+ }
+
private Bundle addAccountWithTypeOnce(String type) throws Exception {
return mAccountManager.addAccount(
type,
@@ -312,13 +305,23 @@
/* handler= */ null).getResult();
}
+ private Bundle addAccountWithTypeOnce(TestAppInstance testApp, String type)
+ throws Exception {
+ return testApp.accountManager().addAccount(
+ type,
+ /* authTokenType= */ null,
+ /* requiredFeatures= */ null,
+ /* addAccountOptions= */ null,
+ /* activity= */ null,
+ /* callback= */ null,
+ /* handler= */ null).getResult();
+ }
+
/**
* Blocks until {@code account} is removed.
*/
- // TODO(b/199077745): Remove sleep once AccountManager race condition is fixed
- private Bundle removeAccount(Account account)
- throws OperationCanceledException, IOException,
- InterruptedException, AuthenticatorException {
+ private Bundle removeAccountFromInstrumentedApp(Account account)
+ throws Exception {
return mAccountManager.removeAccount(
account,
/* activity= */ null,
@@ -326,4 +329,17 @@
/* handler= */ null)
.getResult();
}
+
+ /**
+ * Blocks until {@code account} is removed.
+ */
+ private Bundle removeAccount(TestAppInstance testApp, Account account)
+ throws Exception {
+ return testApp.accountManager().removeAccount(
+ account,
+ /* activity= */ null,
+ /* callback= */ null,
+ /* handler= */ null)
+ .getResult();
+ }
}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/ApplicationRestrictionsTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/ApplicationRestrictionsTest.java
index 0c054e3..ec90f4f 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/ApplicationRestrictionsTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/ApplicationRestrictionsTest.java
@@ -15,6 +15,7 @@
*/
package android.devicepolicy.cts;
+import static android.content.Context.RECEIVER_EXPORTED;
import static android.content.Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED;
import static com.android.bedstead.metricsrecorder.truth.MetricQueryBuilderSubject.assertThat;
@@ -303,7 +304,8 @@
Bundle bundle = createBundle("setApplicationRestrictions_restrictionsChangedBroadcastIsReceived");
try (TestAppInstance testApp = sTestApp.install()) {
- testApp.registerReceiver(new IntentFilter(ACTION_APPLICATION_RESTRICTIONS_CHANGED));
+ testApp.registerReceiver(new IntentFilter(ACTION_APPLICATION_RESTRICTIONS_CHANGED),
+ RECEIVER_EXPORTED);
sDeviceState.dpc().devicePolicyManager()
.setApplicationRestrictions(
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
index 42453f3..8595c70 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
@@ -16,10 +16,10 @@
package android.devicepolicy.cts;
-import static com.android.bedstead.harrier.DeviceState.UserType.PRIMARY_USER;
-import static com.android.bedstead.harrier.DeviceState.UserType.WORK_PROFILE;
import static com.android.bedstead.harrier.OptionalBoolean.FALSE;
import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.UserType.PRIMARY_USER;
+import static com.android.bedstead.harrier.UserType.WORK_PROFILE;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileSharingTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileSharingTest.java
index 3aaee38..791ca42 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileSharingTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileSharingTest.java
@@ -23,8 +23,8 @@
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.os.UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE;
-import static com.android.bedstead.harrier.DeviceState.UserType.PRIMARY_USER;
-import static com.android.bedstead.harrier.DeviceState.UserType.WORK_PROFILE;
+import static com.android.bedstead.harrier.UserType.PRIMARY_USER;
+import static com.android.bedstead.harrier.UserType.WORK_PROFILE;
import static com.android.bedstead.remotedpc.RemoteDpc.DPC_COMPONENT_NAME;
import static com.android.queryable.queries.ActivityQuery.activity;
import static com.android.queryable.queries.IntentFilterQuery.intentFilter;
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/DelegationScopesTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/DelegationScopesTest.java
index 77530c8..3a97388 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/DelegationScopesTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/DelegationScopesTest.java
@@ -23,6 +23,7 @@
import static android.app.admin.DevicePolicyManager.DELEGATION_NETWORK_LOGGING;
import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING;
import static android.app.admin.DevicePolicyManager.EXTRA_DELEGATION_SCOPES;
+import static android.content.Context.RECEIVER_EXPORTED;
import static com.google.common.truth.Truth.assertThat;
@@ -422,7 +423,8 @@
testApp.activities().any().start();
// TODO(b/198588980): automatically register every test app for this broadcast.
testApp.registerReceiver(
- new IntentFilter(ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED));
+ new IntentFilter(ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED),
+ RECEIVER_EXPORTED);
try {
sDeviceState.dpc().devicePolicyManager().setDelegatedScopes(
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/DeviceOwnerPrerequisitesTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/DeviceOwnerPrerequisitesTest.java
index 264dd12..a9ba134 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/DeviceOwnerPrerequisitesTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/DeviceOwnerPrerequisitesTest.java
@@ -30,8 +30,7 @@
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
import com.android.bedstead.harrier.annotations.Postsubmit;
-import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner;
-import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoProfileOwner;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDpc;
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.exceptions.AdbException;
import com.android.bedstead.nene.utils.Poll;
@@ -85,8 +84,7 @@
@Test
@Postsubmit(reason = "new test with sleep")
- @EnsureHasNoDeviceOwner
- @EnsureHasNoProfileOwner
+ @EnsureHasNoDpc
public void setDeviceOwnerViaAdb_deviceHasAccount_fails()
throws InterruptedException {
try (TestAppInstance accountAuthenticatorApp =
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
index 028bb31..80af6af 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
@@ -18,8 +18,19 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCALE;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TIME_ZONE;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID;
+import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC;
+import static android.content.pm.PackageManager.FEATURE_DEVICE_ADMIN;
+import static android.content.pm.PackageManager.FEATURE_MANAGED_USERS;
+import static android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED;
+import static android.nfc.NfcAdapter.EXTRA_NDEF_MESSAGES;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -34,12 +45,16 @@
import android.app.admin.ManagedProfileProvisioningParams;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.CrossProfileApps;
import android.content.pm.PackageManager;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.os.Bundle;
+import android.os.Parcelable;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.Settings;
import androidx.test.core.app.ApplicationProvider;
@@ -47,13 +62,20 @@
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
import com.android.bedstead.harrier.annotations.EnsureDoesNotHavePermission;
+import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile;
import com.android.bedstead.harrier.annotations.EnsureHasPermission;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
import com.android.bedstead.harrier.annotations.Postsubmit;
import com.android.bedstead.harrier.annotations.RequireDoesNotHaveFeature;
import com.android.bedstead.harrier.annotations.RequireFeature;
+import com.android.bedstead.harrier.annotations.RequireNotHeadlessSystemUserMode;
import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner;
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDpc;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoProfileOwner;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasProfileOwner;
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.permissions.PermissionContext;
import com.android.compatibility.common.util.SystemUtil;
@@ -64,8 +86,13 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
@@ -106,14 +133,34 @@
private static final String REMOVE_ACTIVE_ADMIN_COMMAND =
"dpm remove-active-admin --user cur " + DEVICE_ADMIN_COMPONENT_NAME.flattenToString();
+ private static final String NFC_INTENT_COMPONENT_NAME =
+ "com.test.dpc/com.test.dpc.DeviceAdminReceiver";
+ private static final String NFC_INTENT_PACKAGE_NAME =
+ "com.test.dpc.DeviceAdminReceiver";
+ private static final String NFC_INTENT_LOCALE = "en_US";
+ private static final String NFC_INTENT_TIMEZONE = "America/New_York";
+ private static final String NFC_INTENT_WIFI_SSID = "\"" + "TestWifiSsid" + "\"";
+ private static final String NFC_INTENT_WIFI_SECURITY_TYPE = "";
+ private static final String NFC_INTENT_WIFI_PASSWORD = "";
+ private static final String NFC_INTENT_BAD_ACTION = "badAction";
+ private static final String NFC_INTENT_BAD_MIME = "badMime";
+ private static final String NFC_INTENT_PROVISIONING_SAMPLE = "NFC provisioning sample";
+ private static final Intent NFC_INTENT_NO_NDEF_RECORD = new Intent(ACTION_NDEF_DISCOVERED);
+ private static final HashMap<String, String> NFC_DATA_VALID = createNfcIntentData();
+ private static final HashMap<String, String> NFC_DATA_EMPTY = new HashMap();
+ private static final Map<String, String> NFC_DATA_WITH_COMPONENT_NAME =
+ Map.of(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, NFC_INTENT_COMPONENT_NAME);
+ private static final Map<String, String> NFC_DATA_WITH_ADMIN_PACKAGE_NAME =
+ Map.of(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, NFC_INTENT_PACKAGE_NAME);
+
@ClassRule
@Rule
public static final DeviceState sDeviceState = new DeviceState();
@RequireRunOnPrimaryUser
@EnsureHasNoDpc
- @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
- @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+ @RequireFeature(FEATURE_DEVICE_ADMIN)
+ @RequireFeature(FEATURE_MANAGED_USERS)
@EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
@Test
public void newlyProvisionedManagedProfile_createsProfile() throws Exception {
@@ -133,8 +180,8 @@
@RequireRunOnPrimaryUser
@EnsureHasNoDpc
- @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
- @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+ @RequireFeature(FEATURE_DEVICE_ADMIN)
+ @RequireFeature(FEATURE_MANAGED_USERS)
@EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
@Test
public void newlyProvisionedManagedProfile_createsManagedProfile() throws Exception {
@@ -154,8 +201,8 @@
@RequireRunOnPrimaryUser
@EnsureHasNoDpc
- @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
- @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+ @RequireFeature(FEATURE_DEVICE_ADMIN)
+ @RequireFeature(FEATURE_MANAGED_USERS)
@EnsureHasPermission({MANAGE_PROFILE_AND_DEVICE_OWNERS, INTERACT_ACROSS_USERS_FULL})
@Test
public void newlyProvisionedManagedProfile_setsActiveAdmin() throws Exception {
@@ -177,8 +224,8 @@
@RequireRunOnPrimaryUser
@EnsureHasNoDpc
- @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
- @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+ @RequireFeature(FEATURE_DEVICE_ADMIN)
+ @RequireFeature(FEATURE_MANAGED_USERS)
@EnsureHasPermission({MANAGE_PROFILE_AND_DEVICE_OWNERS, INTERACT_ACROSS_USERS})
@Test
public void newlyProvisionedManagedProfile_setsProfileOwner() throws Exception {
@@ -199,8 +246,8 @@
@RequireRunOnPrimaryUser
@EnsureHasNoDpc
- @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
- @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+ @RequireFeature(FEATURE_DEVICE_ADMIN)
+ @RequireFeature(FEATURE_MANAGED_USERS)
@EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
@Ignore
@Test
@@ -224,8 +271,8 @@
@RequireRunOnPrimaryUser
@EnsureHasNoDpc
- @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
- @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+ @RequireFeature(FEATURE_DEVICE_ADMIN)
+ @RequireFeature(FEATURE_MANAGED_USERS)
@EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
@Test
public void newlyProvisionedManagedProfile_removesAccountFromParentByDefault()
@@ -249,8 +296,8 @@
@RequireRunOnPrimaryUser
@EnsureHasNoDpc
- @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
- @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+ @RequireFeature(FEATURE_DEVICE_ADMIN)
+ @RequireFeature(FEATURE_MANAGED_USERS)
@EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
@Ignore
@Test
@@ -261,7 +308,7 @@
ManagedProfileProvisioningParams params =
createManagedProfileProvisioningParamsBuilder()
.setAccountToMigrate(TEST_ACCOUNT)
- .setKeepAccountMigrated(true)
+ .setKeepingAccountOnMigration(true)
.build();
profile = provisionManagedProfile(params);
@@ -275,8 +322,8 @@
@RequireRunOnPrimaryUser
@EnsureHasNoDpc
- @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
- @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+ @RequireFeature(FEATURE_DEVICE_ADMIN)
+ @RequireFeature(FEATURE_MANAGED_USERS)
@EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
@Test
public void newlyProvisionedManagedProfile_removesNonRequiredAppsFromProfile()
@@ -301,8 +348,8 @@
@RequireRunOnPrimaryUser
@EnsureHasNoDpc
- @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
- @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+ @RequireFeature(FEATURE_DEVICE_ADMIN)
+ @RequireFeature(FEATURE_MANAGED_USERS)
@EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
@Test
public void newlyProvisionedManagedProfile_setsCrossProfilePackages()
@@ -412,109 +459,123 @@
@RequireRunOnPrimaryUser
@EnsureHasNoDpc
- @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
+ @RequireFeature(FEATURE_DEVICE_ADMIN)
@EnsureHasPermission({MANAGE_PROFILE_AND_DEVICE_OWNERS})
@Test
public void newlyProvisionedFullyManagedDevice_setsDeviceOwner() throws Exception {
+ boolean setupComplete = TestApis.users().current().getSetupComplete();
+ TestApis.users().current().setSetupComplete(false);
try {
+
FullyManagedDeviceProvisioningParams params =
createDefaultManagedDeviceProvisioningParamsBuilder().build();
- resetUserSetupCompletedFlag();
sDevicePolicyManager.provisionFullyManagedDevice(params);
assertThat(sDevicePolicyManager.isDeviceOwnerApp(sContext.getPackageName())).isTrue();
+
} finally {
sDevicePolicyManager.forceRemoveActiveAdmin(
DEVICE_ADMIN_COMPONENT_NAME, sContext.getUserId());
- setUserSetupCompletedFlag();
+ TestApis.users().current().setSetupComplete(setupComplete);
}
}
@RequireRunOnPrimaryUser
@EnsureHasNoDpc
- @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
+ @RequireFeature(FEATURE_DEVICE_ADMIN)
@EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
@Test
public void newlyProvisionedFullyManagedDevice_doesNotThrowException() throws Exception {
+ boolean setupComplete = TestApis.users().current().getSetupComplete();
+ TestApis.users().current().setSetupComplete(false);
try {
+
FullyManagedDeviceProvisioningParams params =
createDefaultManagedDeviceProvisioningParamsBuilder().build();
- resetUserSetupCompletedFlag();
sDevicePolicyManager.provisionFullyManagedDevice(params);
+
} finally {
sDevicePolicyManager.forceRemoveActiveAdmin(
DEVICE_ADMIN_COMPONENT_NAME, sContext.getUserId());
- setUserSetupCompletedFlag();
+ TestApis.users().current().setSetupComplete(setupComplete);
}
}
@RequireRunOnPrimaryUser
@EnsureHasNoDpc
- @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
+ @RequireFeature(FEATURE_DEVICE_ADMIN)
@EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
@Test
public void newlyProvisionedFullyManagedDevice_canControlSensorPermissionGrantsByDefault()
throws Exception {
+ boolean setupComplete = TestApis.users().current().getSetupComplete();
+ TestApis.users().current().setSetupComplete(false);
try {
+
FullyManagedDeviceProvisioningParams params =
createDefaultManagedDeviceProvisioningParamsBuilder().build();
- resetUserSetupCompletedFlag();
sDevicePolicyManager.provisionFullyManagedDevice(params);
assertThat(sDevicePolicyManager.canAdminGrantSensorsPermissions()).isTrue();
+
} finally {
sDevicePolicyManager.forceRemoveActiveAdmin(
DEVICE_ADMIN_COMPONENT_NAME, sContext.getUserId());
- setUserSetupCompletedFlag();
+ TestApis.users().current().setSetupComplete(setupComplete);
}
}
@RequireRunOnPrimaryUser
@EnsureHasNoDpc
- @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
+ @RequireFeature(FEATURE_DEVICE_ADMIN)
@EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
@Postsubmit(reason = "b/181993922 automatically marked flaky")
@Test
public void newlyProvisionedFullyManagedDevice_canOptOutOfControllingSensorPermissionGrants()
throws Exception {
+ boolean setupComplete = TestApis.users().current().getSetupComplete();
+ TestApis.users().current().setSetupComplete(false);
try {
+
FullyManagedDeviceProvisioningParams params =
createDefaultManagedDeviceProvisioningParamsBuilder()
- .setDeviceOwnerCanGrantSensorsPermissions(false)
+ .setCanDeviceOwnerGrantSensorsPermissions(false)
.build();
- resetUserSetupCompletedFlag();
sDevicePolicyManager.provisionFullyManagedDevice(params);
assertThat(sDevicePolicyManager.canAdminGrantSensorsPermissions()).isFalse();
+
} finally {
sDevicePolicyManager.forceRemoveActiveAdmin(
DEVICE_ADMIN_COMPONENT_NAME, sContext.getUserId());
- setUserSetupCompletedFlag();
+ TestApis.users().current().setSetupComplete(setupComplete);
}
}
@RequireRunOnPrimaryUser
@EnsureHasNoDpc
- @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
+ @RequireFeature(FEATURE_DEVICE_ADMIN)
@EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
@Test
public void newlyProvisionedFullyManagedDevice_leavesAllSystemAppsEnabledWhenRequested()
throws Exception {
+ boolean setupComplete = TestApis.users().current().getSetupComplete();
+ TestApis.users().current().setSetupComplete(false);
try {
+ Set<String> systemAppsBeforeProvisioning = findSystemApps();
+
FullyManagedDeviceProvisioningParams params =
createDefaultManagedDeviceProvisioningParamsBuilder()
.setLeaveAllSystemAppsEnabled(true)
.build();
- resetUserSetupCompletedFlag();
sDevicePolicyManager.provisionFullyManagedDevice(params);
- Set<String> systemAppsBeforeProvisioning = findSystemApps();
Set<String> systemAppsAfterProvisioning = findSystemApps();
assertThat(systemAppsAfterProvisioning).isEqualTo(systemAppsBeforeProvisioning);
} finally {
sDevicePolicyManager.forceRemoveActiveAdmin(
DEVICE_ADMIN_COMPONENT_NAME, sContext.getUserId());
- setUserSetupCompletedFlag();
+ TestApis.users().current().setSetupComplete(setupComplete);
}
}
@@ -538,20 +599,6 @@
.setLeaveAllSystemAppsEnabled(true);
}
- private void resetUserSetupCompletedFlag() {
- try (PermissionContext p = TestApis.permissions().withPermission(WRITE_SECURE_SETTINGS)) {
- Settings.Secure.putInt(sContext.getContentResolver(), USER_SETUP_COMPLETE_KEY, 0);
- }
- sDevicePolicyManager.forceUpdateUserSetupComplete(sContext.getUserId());
- }
-
- private void setUserSetupCompletedFlag() {
- try (PermissionContext p = TestApis.permissions().withPermission(WRITE_SECURE_SETTINGS)) {
- Settings.Secure.putInt(sContext.getContentResolver(), USER_SETUP_COMPLETE_KEY, 1);
- }
- sDevicePolicyManager.forceUpdateUserSetupComplete(sContext.getUserId());
- }
-
private Set<String> findSystemApps() {
return sPackageManager.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY)
.stream()
@@ -637,6 +684,86 @@
.collect(Collectors.toSet());
}
+ @Test
+ public void createProvisioningIntentFromNfcIntent_validNfcIntent_returnsValidIntent()
+ throws IOException {
+ Intent nfcIntent = createNfcIntentFromMap(NFC_DATA_VALID);
+
+ Intent provisioningIntent =
+ sDevicePolicyManager.createProvisioningIntentFromNfcIntent(nfcIntent);
+
+ assertThat(provisioningIntent).isNotNull();
+ assertThat(provisioningBundleToMap(provisioningIntent.getExtras()))
+ .containsAtLeastEntriesIn(NFC_DATA_VALID);
+ }
+
+ @Test
+ public void createProvisioningIntentFromNfcIntent_noComponentNorPackage_returnsNull()
+ throws IOException {
+ Intent nfcIntent = createNfcIntentFromMap(NFC_DATA_EMPTY);
+
+ Intent provisioningIntent =
+ sDevicePolicyManager.createProvisioningIntentFromNfcIntent(nfcIntent);
+
+ assertThat(provisioningIntent).isNull();
+ }
+
+ @Test
+ public void createProvisioningIntentFromNfcIntent_withComponent_returnsValidIntent()
+ throws IOException {
+ Intent nfcIntent = createNfcIntentFromMap(NFC_DATA_WITH_COMPONENT_NAME);
+
+ Intent provisioningIntent =
+ sDevicePolicyManager.createProvisioningIntentFromNfcIntent(nfcIntent);
+
+ assertThat(provisioningIntent).isNotNull();
+ assertThat(provisioningBundleToMap(provisioningIntent.getExtras()))
+ .containsAtLeastEntriesIn(NFC_DATA_WITH_COMPONENT_NAME);
+ }
+
+ @Test
+ public void createProvisioningIntentFromNfcIntent_withPackage_returnsValidIntent()
+ throws IOException {
+ Intent nfcIntent = createNfcIntentFromMap(NFC_DATA_WITH_ADMIN_PACKAGE_NAME);
+
+ Intent provisioningIntent =
+ sDevicePolicyManager.createProvisioningIntentFromNfcIntent(nfcIntent);
+
+ assertThat(provisioningIntent).isNotNull();
+ assertThat(provisioningBundleToMap(provisioningIntent.getExtras()))
+ .containsAtLeastEntriesIn(NFC_DATA_WITH_ADMIN_PACKAGE_NAME);
+ }
+
+ @Test
+ public void createProvisioningIntentFromNfcIntent_badIntentAction_returnsNull()
+ throws IOException {
+ Intent nfcIntent = createNfcIntentWithAction(NFC_INTENT_BAD_ACTION);
+
+ Intent provisioningIntent =
+ sDevicePolicyManager.createProvisioningIntentFromNfcIntent(nfcIntent);
+
+ assertThat(provisioningIntent).isNull();
+ }
+
+ @Test
+ public void createProvisioningIntentFromNfcIntent_badMimeType_returnsNull()
+ throws IOException {
+ Intent nfcIntent = createNfcIntentWithMimeType(NFC_INTENT_BAD_MIME);
+
+ Intent provisioningIntent =
+ sDevicePolicyManager.createProvisioningIntentFromNfcIntent(nfcIntent);
+
+ assertThat(provisioningIntent).isNull();
+ }
+
+ @Test
+ public void createProvisioningIntentFromNfcIntent_doesNotIncludeNdefRecord_returnsNull() {
+ Intent provisioningIntent = sDevicePolicyManager
+ .createProvisioningIntentFromNfcIntent(NFC_INTENT_NO_NDEF_RECORD);
+
+ assertThat(provisioningIntent).isNull();
+ }
+
@EnsureHasDeviceOwner
@Test
public void getCameraDisabled_adminPassedDoesNotBelongToCaller_throwsException() {
@@ -667,4 +794,419 @@
sDevicePolicyManager.removeActiveAdmin(
sDeviceState.deviceOwner().componentName());
}
+
+ private static HashMap<String, String> createNfcIntentData() {
+ HashMap<String, String> nfcIntentInput = new HashMap<String, String>();
+ nfcIntentInput.putAll(
+ Map.of(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, NFC_INTENT_COMPONENT_NAME,
+ EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, NFC_INTENT_PACKAGE_NAME,
+ EXTRA_PROVISIONING_LOCALE, NFC_INTENT_LOCALE,
+ EXTRA_PROVISIONING_TIME_ZONE, NFC_INTENT_TIMEZONE,
+ EXTRA_PROVISIONING_WIFI_SSID, NFC_INTENT_WIFI_SSID,
+ EXTRA_PROVISIONING_WIFI_SECURITY_TYPE, NFC_INTENT_WIFI_SECURITY_TYPE,
+ EXTRA_PROVISIONING_WIFI_PASSWORD, NFC_INTENT_WIFI_PASSWORD)
+ );
+
+ return nfcIntentInput;
+ }
+
+ private Intent createNfcIntentWithAction(String action)
+ throws IOException {
+ return createNfcIntent(NFC_DATA_VALID, action, MIME_TYPE_PROVISIONING_NFC);
+ }
+
+ private Intent createNfcIntentWithMimeType(String mime)
+ throws IOException {
+ return createNfcIntent(NFC_DATA_VALID, ACTION_NDEF_DISCOVERED, mime);
+ }
+
+ private Intent createNfcIntentFromMap(Map<String, String> input)
+ throws IOException {
+ return createNfcIntent(input, ACTION_NDEF_DISCOVERED, MIME_TYPE_PROVISIONING_NFC);
+ }
+
+ private Intent createNfcIntent(Map<String, String> input, String action, String mime)
+ throws IOException {
+ Intent nfcIntent = new Intent(action);
+ Parcelable[] nfcMessages =
+ new Parcelable[]{createNdefMessage(input, mime)};
+ nfcIntent.putExtra(EXTRA_NDEF_MESSAGES, nfcMessages);
+
+ return nfcIntent;
+ }
+
+ private Map<String, String> provisioningBundleToMap(Bundle bundle) {
+ Map<String, String> map = new HashMap();
+
+ for (String key : bundle.keySet()) {
+ if(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME.equals(key)) {
+ ComponentName componentName = bundle.getParcelable(key);
+ map.put(key, componentName.getPackageName() + "/" + componentName.getClassName());
+ }
+ else {
+ map.put(key, bundle.getString(key));
+ }
+ }
+
+ return map;
+ }
+
+ private NdefMessage createNdefMessage(Map<String, String> provisioningValues, String mime)
+ throws IOException {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ Properties properties = new Properties();
+ // Store all the values into the Properties object
+ for (Map.Entry<String, String> e : provisioningValues.entrySet()) {
+ properties.put(e.getKey(), e.getValue());
+ }
+
+ properties.store(stream, NFC_INTENT_PROVISIONING_SAMPLE);
+ NdefRecord record = NdefRecord.createMime(mime, stream.toByteArray());
+
+ return new NdefMessage(new NdefRecord[]{record});
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureDoesNotHavePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ public void checkProvisioningPreCondition_withoutRequiredPermission_throwsSecurityException() {
+ assertThrows(SecurityException.class, () ->
+ sDevicePolicyManager.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DEVICE_ADMIN_COMPONENT_NAME.getPackageName()));
+
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ public void checkProvisioningPreCondition_withRequiredPermission_doesNotThrowSecurityException() {
+ sDevicePolicyManager.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DEVICE_ADMIN_COMPONENT_NAME.getPackageName());
+
+ // Doesn't throw exception.
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @RequireDoesNotHaveFeature(FEATURE_DEVICE_ADMIN)
+ public void checkProvisioningPreCondition_withoutDeviceAdminFeature_returnsDeviceAdminNotSupported() {
+ assertThat(
+ sDevicePolicyManager.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+ .isEqualTo(DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @RequireRunOnPrimaryUser
+ @EnsureHasNoDpc
+ public void checkProvisioningPreCondition_actionPO_returnsOk() {
+ assertThat(
+ sDevicePolicyManager.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+ .isEqualTo(DevicePolicyManager.CODE_OK);
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @RequireDoesNotHaveFeature(FEATURE_MANAGED_USERS)
+ public void checkProvisioningPreCondition_actionPO_withoutManagedUserFeature_returnsManagedUsersNotSupported() {
+ assertThat(
+ sDevicePolicyManager.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+ .isEqualTo(DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED);
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @EnsureHasProfileOwner
+ @RequireRunOnSecondaryUser
+ public void checkProvisioningPreCondition_actionPO_onManagedUser_returnsHasProfileOwner() {
+ assertThat(
+ sDevicePolicyManager.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+ .isEqualTo(DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER);
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @RequireRunOnWorkProfile
+ public void checkProvisioningPreCondition_actionPO_onManagedProfile_returnsHasProfileOwner() {
+ assertThat(
+ sDevicePolicyManager.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+ .isEqualTo(DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER);
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @EnsureHasDeviceOwner
+ public void checkProvisioningPreCondition_actionPO_onManagedDevice_returnsCanNotAddManagedProfile() {
+ assertThat(
+ sDevicePolicyManager.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+ .isEqualTo(DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ public void checkProvisioningPreCondition_actionPO_withWorkProfile_returnsCanNotAddManagedProfile() {
+ assertThat(
+ sDevicePolicyManager.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+ .isEqualTo(DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @RequireRunOnPrimaryUser
+ @EnsureHasNoDpc
+ @RequireNotHeadlessSystemUserMode
+ public void checkProvisioningPreCondition_actionDO_returnsOk() {
+ boolean setupComplete = TestApis.users().current().getSetupComplete();
+ TestApis.users().current().setSetupComplete(false);
+
+ try {
+ assertThat(
+ sDevicePolicyManager.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
+ DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+ .isEqualTo(DevicePolicyManager.CODE_OK);
+
+ } finally {
+ TestApis.users().current().setSetupComplete(setupComplete);
+ }
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @RequireRunOnPrimaryUser
+ @EnsureHasNoDpc
+ @RequireNotHeadlessSystemUserMode
+ public void checkProvisioningPreCondition_actionDO_setupComplete_returnsUserSetupCompleted() {
+ boolean setupComplete = TestApis.users().current().getSetupComplete();
+ TestApis.users().current().setSetupComplete(true);
+
+ try {
+ assertThat(
+ sDevicePolicyManager.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
+ DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+ .isEqualTo(DevicePolicyManager.CODE_USER_SETUP_COMPLETED);
+
+ } finally {
+ TestApis.users().current().setSetupComplete(setupComplete);
+ }
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @RequireRunOnPrimaryUser
+ @EnsureHasDeviceOwner
+ @EnsureHasNoWorkProfile
+ @RequireNotHeadlessSystemUserMode
+ public void checkProvisioningPreCondition_actionDO_onManagedDevice_returnsHasDeviceOwner() {
+ boolean setupComplete = TestApis.users().current().getSetupComplete();
+ TestApis.users().current().setSetupComplete(false);
+
+ try {
+ assertThat(
+ sDevicePolicyManager.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
+ DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+ .isEqualTo(DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
+
+ } finally {
+ TestApis.users().current().setSetupComplete(setupComplete);
+ }
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @RequireRunOnWorkProfile
+ @RequireNotHeadlessSystemUserMode
+ public void checkProvisioningPreCondition_actionDO_onManagedProfile_returnsHasProfileOwner() {
+ boolean setupComplete = TestApis.users().current().getSetupComplete();
+ TestApis.users().current().setSetupComplete(false);
+
+ try {
+ assertThat(
+ sDevicePolicyManager.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
+ DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+ .isEqualTo(DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER);
+
+ } finally {
+ TestApis.users().current().setSetupComplete(setupComplete);
+ }
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @RequireRunOnSecondaryUser
+ @EnsureHasProfileOwner
+ @RequireNotHeadlessSystemUserMode
+ public void checkProvisioningPreCondition_actionDO_onManagedUser_returnsHasProfileOwner() {
+ boolean setupComplete = TestApis.users().current().getSetupComplete();
+ TestApis.users().current().setSetupComplete(false);
+
+ try {
+ assertThat(
+ sDevicePolicyManager.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
+ DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+ .isEqualTo(DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER);
+
+ } finally {
+ TestApis.users().current().setSetupComplete(setupComplete);
+ }
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @RequireRunOnSecondaryUser
+ @EnsureHasNoProfileOwner
+ @RequireNotHeadlessSystemUserMode
+ public void checkProvisioningPreCondition_actionDO_onNonSystemUser_returnsNotSystemUser() {
+ boolean setupComplete = TestApis.users().current().getSetupComplete();
+ TestApis.users().current().setSetupComplete(false);
+
+ try {
+ assertThat(
+ sDevicePolicyManager.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
+ DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+ .isEqualTo(DevicePolicyManager.CODE_NOT_SYSTEM_USER);
+
+ } finally {
+ TestApis.users().current().setSetupComplete(setupComplete);
+ }
+ }
+
+ // TODO(b/208843126): add more CTS coverage for setUserProvisioningState
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureDoesNotHavePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ public void setUserProvisioningState_withoutRequiredPermission_throwsSecurityException() {
+ assertThrows(SecurityException.class, () ->
+ sDevicePolicyManager.setUserProvisioningState(
+ DevicePolicyManager.STATE_USER_UNMANAGED,
+ TestApis.users().current().userHandle()));
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @RequireRunOnWorkProfile
+ public void setUserProvisioningState_withRequiredPermission_doesNotThrowSecurityException() {
+ sDevicePolicyManager.setUserProvisioningState(
+ DevicePolicyManager.STATE_USER_PROFILE_FINALIZED,
+ TestApis.users().current().userHandle());
+
+ // Doesn't throw exception.
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @EnsureHasNoDpc
+ public void setUserProvisioningState_unmanagedDevice_stateUserSetupIncomplete_throwsIllegalStateException() {
+ assertThrows(IllegalStateException.class, () ->
+ sDevicePolicyManager.setUserProvisioningState(
+ DevicePolicyManager.STATE_USER_SETUP_INCOMPLETE,
+ TestApis.users().current().userHandle()));
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @EnsureHasNoDpc
+ public void setUserProvisioningState_unmanagedDevice_stateUserSetupComplete_throwsIllegalStateException() {
+ assertThrows(IllegalStateException.class, () ->
+ sDevicePolicyManager.setUserProvisioningState(
+ DevicePolicyManager.STATE_USER_SETUP_COMPLETE,
+ TestApis.users().current().userHandle()));
+
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @EnsureHasNoDpc
+ public void setUserProvisioningState_unmanagedDevice_stateUserSetupFinalized_throwsIllegalStateException() {
+ assertThrows(IllegalStateException.class, () ->
+ sDevicePolicyManager.setUserProvisioningState(
+ DevicePolicyManager.STATE_USER_SETUP_FINALIZED,
+ TestApis.users().current().userHandle()));
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @EnsureHasNoDpc
+ public void setUserProvisioningState_unmanagedDevice_stateUserProfileComplete_throwsIllegalStateException() {
+ assertThrows(IllegalStateException.class, () ->
+ sDevicePolicyManager.setUserProvisioningState(
+ DevicePolicyManager.STATE_USER_PROFILE_COMPLETE,
+ TestApis.users().current().userHandle()));
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @EnsureHasNoDpc
+ public void setUserProvisioningState_unmanagedDevice_stateUserProfileFinalized_throwsIllegalStateException() {
+ assertThrows(IllegalStateException.class, () ->
+ sDevicePolicyManager.setUserProvisioningState(
+ DevicePolicyManager.STATE_USER_PROFILE_FINALIZED,
+ TestApis.users().current().userHandle()));
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @EnsureHasNoDpc
+ public void setUserProvisioningState_settingToSameState_throwIllegalStateException() {
+ assertThrows(IllegalStateException.class, () ->
+ sDevicePolicyManager.setUserProvisioningState(
+ DevicePolicyManager.STATE_USER_UNMANAGED,
+ TestApis.users().current().userHandle()));
+ }
+
+ @Postsubmit(reason = "New test")
+ @Test
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @EnsureHasDeviceOwner
+ public void setUserProvisioningState_unmanagedDevice_stateUserUnmanaged_doesNotThrowIllegalStateException() {
+ sDevicePolicyManager.setUserProvisioningState(
+ DevicePolicyManager.STATE_USER_PROFILE_FINALIZED,
+ TestApis.users().current().userHandle());
+
+ assertThat(sDevicePolicyManager.getUserProvisioningState())
+ .isEqualTo(DevicePolicyManager.STATE_USER_PROFILE_FINALIZED);
+ }
}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/EnterpriseResourcesTests.java b/tests/devicepolicy/src/android/devicepolicy/cts/EnterpriseResourcesTests.java
new file mode 100644
index 0000000..fac8337
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/EnterpriseResourcesTests.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.devicepolicy.cts;
+
+import static android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES;
+import static android.app.admin.DevicePolicyResources.Drawable.INVALID_ID;
+import static android.app.admin.DevicePolicyResources.Drawable.Style.SOLID_COLORED;
+import static android.app.admin.DevicePolicyResources.Drawable.WORK_PROFILE_ICON;
+import static android.app.admin.DevicePolicyResources.Drawable.WORK_PROFILE_ICON_BADGE;
+import static android.app.admin.DevicePolicyResources.Drawable.WORK_PROFILE_OFF_ICON;
+import static android.app.admin.DevicePolicyResources.Drawable.WORK_PROFILE_USER_ICON;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.app.admin.DevicePolicyDrawableResource;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureDoesNotHavePermission;
+import com.android.bedstead.harrier.annotations.EnsureHasPermission;
+import com.android.bedstead.harrier.annotations.Postsubmit;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.permissions.PermissionContext;
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+// TODO(b/208084779): Add more cts tests to cover setting different styles and sources, also
+// add more tests to cover calling from other packages after adding support for the new APIs in
+// the test sdk.
+@RunWith(BedsteadJUnit4.class)
+public class EnterpriseResourcesTests {
+ private static final String TAG = "EnterpriseResourcesTests";
+
+ private static final Context sContext = TestApis.context().instrumentedContext();
+ private static final DevicePolicyManager sDpm =
+ sContext.getSystemService(DevicePolicyManager.class);
+
+ private static final int UPDATABLE_DRAWABLE_1 = WORK_PROFILE_ICON_BADGE;
+ private static final int UPDATABLE_DRAWABLE_2 = WORK_PROFILE_ICON;
+ private static final int DRAWABLE_STYLE_1 = SOLID_COLORED;
+ private static final int INVALID_DRAWABLE_RESOURCE_ID = -1;
+
+ @ClassRule
+ @Rule
+ public static final DeviceState sDeviceState = new DeviceState();
+
+ @After
+ public void tearDown() {
+ resetAllDrawables();
+ }
+
+ @Before
+ public void setup() {
+ resetAllDrawables();
+ }
+
+ private void resetAllDrawables() {
+ try (PermissionContext p = TestApis.permissions().withPermission(
+ UPDATE_DEVICE_MANAGEMENT_RESOURCES)) {
+ sDpm.resetDrawables(
+ new int[]{
+ WORK_PROFILE_ICON_BADGE,
+ WORK_PROFILE_ICON,
+ WORK_PROFILE_OFF_ICON,
+ WORK_PROFILE_USER_ICON});
+ }
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureDoesNotHavePermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ public void setDrawables_withoutRequiredPermission_throwsSecurityException() {
+ assertThrows(SecurityException.class, () ->
+ sDpm.setDrawables(createDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1)));
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ public void setDrawables_withRequiredPermission_doesNotThrowSecurityException() {
+ sDpm.setDrawables(createDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ public void setDrawables_withInvalidUpdatableDrawableId_throwsIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, () ->
+ sDpm.setDrawables(createDrawable(
+ INVALID_ID, DRAWABLE_STYLE_1, R.drawable.test_drawable_1)));
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ public void setDrawables_updatesCorrectUpdatableDrawable() {
+ sDpm.setDrawables(createDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+
+ Drawable drawable = sDpm.getDrawable(
+ UPDATABLE_DRAWABLE_1,
+ DRAWABLE_STYLE_1,
+ /* default= */ () -> null);
+ assertThat(areSameDrawables(drawable, sContext.getDrawable(R.drawable.test_drawable_1)))
+ .isTrue();
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ public void setDrawables_updatesCurrentlyUpdatedDrawable() {
+ sDpm.setDrawables(createDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+
+ sDpm.setDrawables(createDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_2));
+
+ Drawable drawable = sDpm.getDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, /* default= */ () -> null);
+ assertThat(areSameDrawables(drawable, sContext.getDrawable(R.drawable.test_drawable_2)))
+ .isTrue();
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ public void setDrawables_doesNotUpdateOtherUpdatableDrawables() {
+ sDpm.resetDrawables(new int[]{UPDATABLE_DRAWABLE_2});
+
+ sDpm.setDrawables(createDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+
+ assertThat(
+ sDpm.getDrawable(UPDATABLE_DRAWABLE_2, DRAWABLE_STYLE_1, /* default= */ () -> null))
+ .isNull();
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ public void setDrawables_drawableChangedFromNull_sendsBroadcast() {
+ sDpm.resetDrawables(new int[]{UPDATABLE_DRAWABLE_1});
+ BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+ DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
+
+ sDpm.setDrawables(createDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+
+ broadcastReceiver.awaitForBroadcastOrFail();
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ public void setDrawables_drawableChangedFromOtherDrawable_sendsBroadcast() {
+ sDpm.setDrawables(createDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+ BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+ DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
+
+ sDpm.setDrawables(createDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_2));
+
+ broadcastReceiver.awaitForBroadcastOrFail();
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ @Ignore("b/208237942")
+ public void setDrawables_drawableNotChanged_doesNotSendBroadcast() {
+ sDpm.setDrawables(createDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+ BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+ DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
+
+ sDpm.setDrawables(createDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+
+ assertThat(broadcastReceiver.awaitForBroadcast()).isNull();
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureDoesNotHavePermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ public void resetDrawables_withoutRequiredPermission_throwsSecurityException() {
+ assertThrows(SecurityException.class, () -> sDpm.resetDrawables(
+ new int[]{UPDATABLE_DRAWABLE_1}));
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ public void resetDrawables_withRequiredPermission_doesNotThrowSecurityException() {
+ sDpm.resetDrawables(new int[]{UPDATABLE_DRAWABLE_1});
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ public void resetDrawables_removesPreviouslysetDrawables() {
+ sDpm.setDrawables(createDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+
+ sDpm.resetDrawables(new int[]{UPDATABLE_DRAWABLE_1});
+
+ assertThat(
+ sDpm.getDrawable(UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, /* default= */ () -> null))
+ .isNull();
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ public void resetDrawables_doesNotResetOtherSetDrawables() {
+ sDpm.setDrawables(createDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+ sDpm.setDrawables(createDrawable(
+ UPDATABLE_DRAWABLE_2, DRAWABLE_STYLE_1, R.drawable.test_drawable_2));
+
+ sDpm.resetDrawables(new int[]{UPDATABLE_DRAWABLE_1});
+
+ Drawable drawable = sDpm.getDrawable(
+ UPDATABLE_DRAWABLE_2, DRAWABLE_STYLE_1, /* default= */ () -> null);
+ assertThat(areSameDrawables(drawable, sContext.getDrawable(R.drawable.test_drawable_2)))
+ .isTrue();
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ public void resetDrawables_drawableChanged_sendsBroadcast() {
+ sDpm.setDrawables(createDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+ BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+ DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
+
+ sDpm.resetDrawables(new int[]{UPDATABLE_DRAWABLE_1});
+
+ broadcastReceiver.awaitForBroadcastOrFail();
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ @Ignore("b/208237942")
+ public void resetDrawables_drawableNotChanged_doesNotSendBroadcast() {
+ sDpm.resetDrawables(new int[]{UPDATABLE_DRAWABLE_1});
+ BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+ DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
+
+ sDpm.resetDrawables(new int[]{UPDATABLE_DRAWABLE_1});
+
+ assertThat(broadcastReceiver.awaitForBroadcast()).isNull();
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ public void getDrawable_drawableIsSet_returnsUpdatedDrawable() {
+ try (PermissionContext p = TestApis.permissions().withPermission(
+ UPDATE_DEVICE_MANAGEMENT_RESOURCES)) {
+ sDpm.setDrawables(createDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+ }
+
+ Drawable drawable = sDpm.getDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, /* default= */ () -> null);
+
+ assertThat(areSameDrawables(drawable, sContext.getDrawable(R.drawable.test_drawable_1)))
+ .isTrue();
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ public void getDrawable_drawableIsNotSet_returnsDefaultDrawable() {
+ Drawable defaultDrawable = sContext.getDrawable(R.drawable.test_drawable_1);
+
+ Drawable drawable = sDpm.getDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, /* default= */ () -> defaultDrawable);
+
+ assertThat(drawable).isEqualTo(defaultDrawable);
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ public void getDrawable_defaultIsNull_throwsException() {
+ assertThrows(NullPointerException.class, () -> sDpm.getDrawable(
+ UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, null));
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ public void constructDevicePolicyDrawableResource_withNonExistentDrawable_throwsIllegalStateException() {
+ assertThrows(IllegalStateException.class, () -> new DevicePolicyDrawableResource(
+ sContext, UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, INVALID_DRAWABLE_RESOURCE_ID));
+ }
+
+ @Test
+ @Postsubmit(reason = "New test")
+ @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+ public void constructDevicePolicyDrawableResourc_withNonDrawableResource_throwsIllegalStateException() {
+ assertThrows(IllegalStateException.class, () -> new DevicePolicyDrawableResource(
+ sContext, UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.string.test_string));
+ }
+
+ // TODO(b/16348282): extract to a common place to make it reusable.
+ private static boolean areSameDrawables(Drawable drawable1, Drawable drawable2) {
+ return drawable1 == drawable2 || getBitmap(drawable1).sameAs(getBitmap(drawable2));
+ }
+
+ private static Bitmap getBitmap(Drawable drawable) {
+ int width = drawable.getIntrinsicWidth();
+ int height = drawable.getIntrinsicHeight();
+ // Some drawables have no intrinsic width - e.g. solid colours.
+ if (width <= 0) {
+ width = 1;
+ }
+ if (height <= 0) {
+ height = 1;
+ }
+ Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(result);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return result;
+ }
+
+ private Set<DevicePolicyDrawableResource> createDrawable(
+ int updatableDrawableId, int style, int resourceId) {
+ return Set.of(new DevicePolicyDrawableResource(
+ sContext, updatableDrawableId, style, resourceId));
+ }
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/LockTaskTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/LockTaskTest.java
index 6f9128d..ae071c2 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/LockTaskTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/LockTaskTest.java
@@ -54,6 +54,7 @@
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.IntTestParameter;
import com.android.bedstead.harrier.annotations.Postsubmit;
import com.android.bedstead.harrier.annotations.RequireFeature;
import com.android.bedstead.harrier.annotations.enterprise.CannotSetPolicyTest;
@@ -76,6 +77,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Set;
@RunWith(BedsteadJUnit4.class)
@@ -88,20 +91,24 @@
private static final DevicePolicyManager sLocalDevicePolicyManager =
TestApis.context().instrumentedContext().getSystemService(DevicePolicyManager.class);
- private static final int[] INDIVIDUALLY_SETTABLE_FLAGS = new int[]{
+ @IntTestParameter({
LOCK_TASK_FEATURE_SYSTEM_INFO,
LOCK_TASK_FEATURE_HOME,
LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
- LOCK_TASK_FEATURE_KEYGUARD
- };
+ LOCK_TASK_FEATURE_KEYGUARD})
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface IndividuallySettableFlagTestParameter {
+ }
- private static final int[] FLAGS_SETTABLE_WITH_HOME = new int[]{
+ @IntTestParameter({
LOCK_TASK_FEATURE_SYSTEM_INFO,
LOCK_TASK_FEATURE_OVERVIEW,
LOCK_TASK_FEATURE_NOTIFICATIONS,
LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
- LOCK_TASK_FEATURE_KEYGUARD
- };
+ LOCK_TASK_FEATURE_KEYGUARD})
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface SettableWithHomeFlagTestParameter {
+ }
private static final TestAppProvider sTestAppProvider = new TestAppProvider();
private static final TestApp sLockTaskTestApp = sTestAppProvider.query()
@@ -322,20 +329,19 @@
@PositivePolicyTest(policy = LockTask.class)
// TODO(b/188893663): Support additional parameterization for cases like this
@Postsubmit(reason = "b/181993922 automatically marked flaky")
- public void setLockTaskFeatures_overviewFeature_setsFeature() {
+ public void setLockTaskFeatures_individuallySettableFlag_setsFeature(
+ @IndividuallySettableFlagTestParameter int flag) {
int originalLockTaskFeatures =
sDeviceState.dpc().devicePolicyManager()
.getLockTaskFeatures(DPC_COMPONENT_NAME);
try {
- for (int flag : INDIVIDUALLY_SETTABLE_FLAGS) {
- sDeviceState.dpc().devicePolicyManager()
- .setLockTaskFeatures(DPC_COMPONENT_NAME, flag);
+ sDeviceState.dpc().devicePolicyManager()
+ .setLockTaskFeatures(DPC_COMPONENT_NAME, flag);
- assertThat(sDeviceState.dpc().devicePolicyManager()
- .getLockTaskFeatures(DPC_COMPONENT_NAME))
- .isEqualTo(flag);
- }
+ assertThat(sDeviceState.dpc().devicePolicyManager()
+ .getLockTaskFeatures(DPC_COMPONENT_NAME))
+ .isEqualTo(flag);
} finally {
sDeviceState.dpc().devicePolicyManager()
.setLockTaskFeatures(DPC_COMPONENT_NAME, originalLockTaskFeatures);
@@ -387,20 +393,19 @@
@PositivePolicyTest(policy = LockTask.class)
// TODO(b/188893663): Support additional parameterization for cases like this
@Postsubmit(reason = "b/181993922 automatically marked flaky")
- public void setLockTaskFeatures_multipleFeatures_setsFeatures() {
+ public void setLockTaskFeatures_multipleFeatures_setsFeatures(
+ @SettableWithHomeFlagTestParameter int flag) {
int originalLockTaskFeatures =
sDeviceState.dpc().devicePolicyManager()
.getLockTaskFeatures(DPC_COMPONENT_NAME);
try {
- for (int flag : FLAGS_SETTABLE_WITH_HOME) {
- sDeviceState.dpc().devicePolicyManager()
- .setLockTaskFeatures(DPC_COMPONENT_NAME, LOCK_TASK_FEATURE_HOME | flag);
+ sDeviceState.dpc().devicePolicyManager()
+ .setLockTaskFeatures(DPC_COMPONENT_NAME, LOCK_TASK_FEATURE_HOME | flag);
- assertThat(sDeviceState.dpc().devicePolicyManager()
- .getLockTaskFeatures(DPC_COMPONENT_NAME))
- .isEqualTo(LOCK_TASK_FEATURE_HOME | flag);
- }
+ assertThat(sDeviceState.dpc().devicePolicyManager()
+ .getLockTaskFeatures(DPC_COMPONENT_NAME))
+ .isEqualTo(LOCK_TASK_FEATURE_HOME | flag);
} finally {
sDeviceState.dpc().devicePolicyManager()
.setLockTaskFeatures(DPC_COMPONENT_NAME, originalLockTaskFeatures);
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/LockTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/LockTest.java
new file mode 100644
index 0000000..74d8ebc
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/LockTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.devicepolicy.cts;
+
+import static android.Manifest.permission.LOCK_DEVICE;
+import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
+import static android.os.Build.VERSION_CODES.N;
+import static android.os.Build.VERSION_CODES.O;
+
+import static com.android.bedstead.harrier.Defaults.DEFAULT_PASSWORD;
+import static com.android.bedstead.metricsrecorder.truth.MetricQueryBuilderSubject.assertThat;
+import static com.android.bedstead.remotedpc.RemoteDpc.DPC_COMPONENT_NAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.KeyguardManager;
+import android.app.admin.DevicePolicyManager;
+import android.stats.devicepolicy.EventId;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasPermission;
+import com.android.bedstead.harrier.annotations.EnsurePasswordNotSet;
+import com.android.bedstead.harrier.annotations.EnsurePasswordSet;
+import com.android.bedstead.harrier.annotations.EnsureScreenIsOn;
+import com.android.bedstead.harrier.annotations.Postsubmit;
+import com.android.bedstead.harrier.annotations.RequireDoesNotHaveFeature;
+import com.android.bedstead.harrier.annotations.RequireFeature;
+import com.android.bedstead.harrier.annotations.RequireTargetSdkVersion;
+import com.android.bedstead.harrier.annotations.enterprise.PositivePolicyTest;
+import com.android.bedstead.harrier.policies.DeprecatedResetPassword;
+import com.android.bedstead.harrier.policies.LockNow;
+import com.android.bedstead.metricsrecorder.EnterpriseMetricsRecorder;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.utils.Poll;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(BedsteadJUnit4.class)
+@RequireFeature("android.software.secure_lock_screen")
+public class LockTest {
+
+ @ClassRule @Rule
+ public static final DeviceState sDeviceState = new DeviceState();
+
+ private static final DevicePolicyManager sLocalDevicePolicyManager =
+ TestApis.context().instrumentedContext().getSystemService(DevicePolicyManager.class);
+ private static final KeyguardManager sLocalKeyguardManager =
+ TestApis.context().instrumentedContext().getSystemService(KeyguardManager.class);
+
+ // TODO(191637162): When @PositivePolicyTest supports permissions, remove
+ @RequireFeature("android.software.secure_lock_screen")
+ @RequireDoesNotHaveFeature(FEATURE_AUTOMOTIVE)
+ @EnsureHasPermission(LOCK_DEVICE)
+ @EnsureScreenIsOn
+ @EnsurePasswordNotSet
+ @Postsubmit(reason = "New test")
+ @Test
+ public void lockNow_permission_noPasswordSet_turnsScreenOff() throws Exception {
+ sLocalDevicePolicyManager.lockNow();
+
+ Poll.forValue("isScreenOn", () -> TestApis.device().isScreenOn())
+ .toBeEqualTo(false)
+ .errorOnFail()
+ .await();
+ }
+
+ // TODO(191637162): When @PositivePolicyTest supports permissions, remove
+ @RequireFeature("android.software.secure_lock_screen")
+ @RequireFeature(FEATURE_AUTOMOTIVE)
+ @EnsureHasPermission(LOCK_DEVICE)
+ @EnsureScreenIsOn
+ @EnsurePasswordNotSet
+ @Postsubmit(reason = "New test")
+ @Test
+ public void lockNow_permission_automotive_noPasswordSet_doesNotTurnScreenOff()
+ throws Exception {
+ sLocalDevicePolicyManager.lockNow();
+
+ assertThat(TestApis.device().isScreenOn()).isTrue();
+ }
+
+ // TODO(191637162): When @PositivePolicyTest supports permissions, remove
+ @RequireFeature("android.software.secure_lock_screen")
+ @RequireDoesNotHaveFeature(FEATURE_AUTOMOTIVE)
+ @EnsureHasPermission(LOCK_DEVICE)
+ @EnsureScreenIsOn
+ @EnsurePasswordSet
+ @Postsubmit(reason = "New test")
+ @Test
+ public void lockNow_permission_passwordSet_locksDevice() throws Exception {
+ sLocalDevicePolicyManager.lockNow();
+
+ Poll.forValue("isDeviceLocked", sLocalKeyguardManager::isDeviceLocked)
+ .toBeEqualTo(true)
+ .errorOnFail()
+ .await();
+ }
+
+ @Test
+ @PositivePolicyTest(policy = LockNow.class)
+ @Postsubmit(reason = "New test")
+ @EnsurePasswordNotSet
+ public void lockNow_logsMetric() {
+ try (EnterpriseMetricsRecorder metrics = EnterpriseMetricsRecorder.create()) {
+ sDeviceState.dpc().devicePolicyManager().lockNow(/* flags= */ 0);
+
+ assertThat(metrics.query()
+ .whereType().isEqualTo(EventId.LOCK_NOW_VALUE)
+ .whereAdminPackageName().isEqualTo(DPC_COMPONENT_NAME.getPackageName())
+ .whereInteger().isEqualTo(0)
+ ).wasLogged();
+ }
+ }
+
+ @RequireDoesNotHaveFeature(FEATURE_AUTOMOTIVE)
+ @EnsureScreenIsOn
+ @EnsurePasswordNotSet
+ @Postsubmit(reason = "New test")
+ @Test
+ @PositivePolicyTest(policy = LockNow.class)
+ public void lockNow_noPasswordSet_turnsScreenOff() throws Exception {
+ sDeviceState.dpc().devicePolicyManager().lockNow();
+
+ Poll.forValue("isScreenOn", () -> TestApis.device().isScreenOn())
+ .toBeEqualTo(false)
+ .errorOnFail()
+ .await();
+ }
+
+ @RequireFeature(FEATURE_AUTOMOTIVE)
+ @EnsureScreenIsOn
+ @EnsurePasswordNotSet
+ @Postsubmit(reason = "New test")
+ @Test
+ @PositivePolicyTest(policy = LockNow.class)
+ public void lockNow_automotive_noPasswordSet_doesNotTurnScreenOff() throws Exception {
+ sDeviceState.dpc().devicePolicyManager().lockNow();
+
+ assertThat(TestApis.device().isScreenOn()).isTrue();
+ }
+
+ @RequireDoesNotHaveFeature(FEATURE_AUTOMOTIVE)
+ @EnsureScreenIsOn
+ @EnsurePasswordSet
+ @Postsubmit(reason = "New test")
+ @Test
+ @PositivePolicyTest(policy = LockNow.class)
+ public void lockNow_passwordSet_locksDevice() throws Exception {
+ sDeviceState.dpc().devicePolicyManager().lockNow();
+
+ Poll.forValue("isDeviceLocked", sLocalKeyguardManager::isDeviceLocked)
+ .toBeEqualTo(true)
+ .errorOnFail()
+ .await();
+ }
+
+ @RequireDoesNotHaveFeature(FEATURE_AUTOMOTIVE)
+ @RequireTargetSdkVersion(max = N)
+ @Test
+ @PositivePolicyTest(policy = DeprecatedResetPassword.class)
+ public void resetPassword_targetBeforeN_returnsFalse() {
+ assertThat(sDeviceState.dpc()
+ .devicePolicyManager().resetPassword(DEFAULT_PASSWORD, /* flags= */ 0)).isFalse();
+ }
+
+ @RequireDoesNotHaveFeature(FEATURE_AUTOMOTIVE)
+ @RequireTargetSdkVersion(min = O)
+ @Test
+ @PositivePolicyTest(policy = DeprecatedResetPassword.class)
+ public void resetPassword_targetAfterO_throwsSecurityException() {
+ assertThrows(SecurityException.class,
+ () -> sDeviceState.dpc().devicePolicyManager()
+ .resetPassword(DEFAULT_PASSWORD, /* flags= */ 0));
+ }
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/PermissionGrantTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/PermissionGrantTest.java
index 4545f94..bcebff2 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/PermissionGrantTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/PermissionGrantTest.java
@@ -41,6 +41,7 @@
import com.android.bedstead.harrier.annotations.AfterClass;
import com.android.bedstead.harrier.annotations.BeforeClass;
import com.android.bedstead.harrier.annotations.NotificationsTest;
+import com.android.bedstead.harrier.annotations.StringTestParameter;
import com.android.bedstead.harrier.annotations.enterprise.CanSetPolicyTest;
import com.android.bedstead.harrier.annotations.enterprise.CannotSetPolicyTest;
import com.android.bedstead.harrier.annotations.enterprise.NegativePolicyTest;
@@ -54,14 +55,15 @@
import com.android.bedstead.testapp.TestAppInstance;
import com.android.bedstead.testapp.TestAppProvider;
-import com.google.common.collect.ImmutableSet;
-
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
@RunWith(BedsteadJUnit4.class)
public final class PermissionGrantTest {
@@ -80,24 +82,40 @@
private static final String DEVELOPMENT_PERMISSION = INTERACT_ACROSS_USERS;
- private static final ImmutableSet<String> SENSOR_PERMISSIONS = ImmutableSet.of(
+ @StringTestParameter({
ACCESS_FINE_LOCATION,
ACCESS_BACKGROUND_LOCATION,
ACCESS_COARSE_LOCATION,
CAMERA,
ACTIVITY_RECOGNITION,
- BODY_SENSORS);
+ BODY_SENSORS})
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface SensorPermissionTestParameter {
+ }
- private static final ImmutableSet<String> LOCATION_PERMISSIONS = ImmutableSet.of(
+ @StringTestParameter({
ACCESS_FINE_LOCATION,
ACCESS_BACKGROUND_LOCATION,
- ACCESS_COARSE_LOCATION);
+ ACCESS_COARSE_LOCATION})
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface LocationPermissionTestParameter {
+ }
- private static final ImmutableSet<String> DENYABLE_PERMISSIONS = ImmutableSet.<String>builder()
- .add(GRANTABLE_PERMISSION)
- .add(READ_SMS) // All DPCs can deny sms permission
- .addAll(SENSOR_PERMISSIONS) // All DPCs can deny sensor permissions
- .build();
+ @StringTestParameter({
+ // Grantable permission
+ READ_CALENDAR,
+ READ_SMS, // All DPCs can deny sms permission
+ // All DPCs can deny sensor permissions
+ ACCESS_FINE_LOCATION,
+ ACCESS_BACKGROUND_LOCATION,
+ ACCESS_COARSE_LOCATION,
+ CAMERA,
+ ACTIVITY_RECOGNITION,
+ BODY_SENSORS
+ })
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface DeniablePermissionTestParameter {
+ }
private static final String NON_EXISTING_PACKAGE_NAME = "non.existing.package";
private static final String NOT_DECLARED_PERMISSION = "not.declared.permission";
@@ -158,62 +176,57 @@
@Test
@NegativePolicyTest(policy = SetSensorPermissionGranted.class)
- public void getPermissionGrantState_sensorPermission_notAbleToSetState_alsoCantReadState() {
- // TODO(b/188893663): Replace with parameterization
- for (String permission : SENSOR_PERMISSIONS) {
- int existingGrantState = sDeviceState.dpc().devicePolicyManager()
- .getPermissionGrantState(sDeviceState.dpc().componentName(),
- sTestApp.packageName(), permission);
- try {
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, PERMISSION_GRANT_STATE_GRANTED);
+ public void getPermissionGrantState_sensorPermission_notAbleToSetState_alsoCantReadState(
+ @SensorPermissionTestParameter String permission) {
+ int existingGrantState = sDeviceState.dpc().devicePolicyManager()
+ .getPermissionGrantState(sDeviceState.dpc().componentName(),
+ sTestApp.packageName(), permission);
+ try {
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, PERMISSION_GRANT_STATE_GRANTED);
- sTestApp.pkg().grantPermission(TestApis.users().instrumented(), permission);
- // TODO(b/204041462): Replace granting the permission here with the user pressing the
- // "deny" button on the permission
+ sTestApp.pkg().grantPermission(TestApis.users().instrumented(), permission);
+ // TODO(b/204041462): Replace granting the permission here with the user pressing the
+ // "deny" button on the permission
- assertWithMessage("Should not be able to read permission grant state but can")
- .that(sDeviceState.dpc().devicePolicyManager().getPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission))
- .isEqualTo(PERMISSION_GRANT_STATE_DEFAULT);
- } finally {
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, existingGrantState);
- sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
- }
+ assertWithMessage("Should not be able to read permission grant state but can")
+ .that(sDeviceState.dpc().devicePolicyManager().getPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission))
+ .isEqualTo(PERMISSION_GRANT_STATE_DEFAULT);
+ } finally {
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, existingGrantState);
+ sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
}
}
@Test
@CanSetPolicyTest(policy = SetPermissionGrantState.class)
- public void denyPermission_setsGrantState() {
- // TODO(b/188893663): Replace with parameterization
- for (String permission : DENYABLE_PERMISSIONS) {
- int existingGrantState = sDeviceState.dpc().devicePolicyManager()
- .getPermissionGrantState(sDeviceState.dpc().componentName(),
- sTestApp.packageName(), permission);
+ public void denyPermission_setsGrantState(@DeniablePermissionTestParameter String permission) {
+ int existingGrantState = sDeviceState.dpc().devicePolicyManager()
+ .getPermissionGrantState(sDeviceState.dpc().componentName(),
+ sTestApp.packageName(), permission);
- try {
- boolean wasSet = sDeviceState.dpc().devicePolicyManager()
- .setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, PERMISSION_GRANT_STATE_DENIED);
+ try {
+ boolean wasSet = sDeviceState.dpc().devicePolicyManager()
+ .setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, PERMISSION_GRANT_STATE_DENIED);
- assertWithMessage("setPermissionGrantState did not return true")
- .that(wasSet).isTrue();
- assertWithMessage("Permission grant state should be set to denied but was not")
- .that(sDeviceState.dpc().devicePolicyManager().getPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission))
- .isEqualTo(PERMISSION_GRANT_STATE_DENIED);
- } finally {
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, existingGrantState);
- }
+ assertWithMessage("setPermissionGrantState did not return true")
+ .that(wasSet).isTrue();
+ assertWithMessage("Permission grant state should be set to denied but was not")
+ .that(sDeviceState.dpc().devicePolicyManager().getPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission))
+ .isEqualTo(PERMISSION_GRANT_STATE_DENIED);
+ } finally {
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, existingGrantState);
}
}
@@ -246,28 +259,26 @@
@Test
@PositivePolicyTest(policy = SetPermissionGrantState.class)
- public void denyPermission_permissionIsDenied() {
- // TODO(b/188893663): Replace with parameterization
- for (String permission : DENYABLE_PERMISSIONS) {
- int existingGrantState = sDeviceState.dpc().devicePolicyManager()
- .getPermissionGrantState(sDeviceState.dpc().componentName(),
- sTestApp.packageName(), permission);
- try {
- sTestApp.pkg().grantPermission(TestApis.users().instrumented(), permission);
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, PERMISSION_GRANT_STATE_DENIED);
+ public void denyPermission_permissionIsDenied(
+ @DeniablePermissionTestParameter String permission) {
+ int existingGrantState = sDeviceState.dpc().devicePolicyManager()
+ .getPermissionGrantState(sDeviceState.dpc().componentName(),
+ sTestApp.packageName(), permission);
+ try {
+ sTestApp.pkg().grantPermission(TestApis.users().instrumented(), permission);
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, PERMISSION_GRANT_STATE_DENIED);
- assertWithMessage("Permission should not be granted but was").that(
- sTestApp.pkg().hasPermission(permission)).isFalse();
+ assertWithMessage("Permission should not be granted but was").that(
+ sTestApp.pkg().hasPermission(permission)).isFalse();
- // TODO(b/204041462): Test that the app cannot request the permission
- } finally {
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, existingGrantState);
- sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
- }
+ // TODO(b/204041462): Test that the app cannot request the permission
+ } finally {
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, existingGrantState);
+ sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
}
}
@@ -297,41 +308,37 @@
@Test
@NegativePolicyTest(policy = SetPermissionGrantState.class)
- public void denyPermission_doesNotApply_permissionIsNotDenied() {
- // TODO(b/188893663): Replace with parameterization
- for (String permission : DENYABLE_PERMISSIONS) {
- try {
- sTestApp.pkg().grantPermission(TestApis.users().instrumented(), permission);
+ public void denyPermission_doesNotApply_permissionIsNotDenied(
+ @DeniablePermissionTestParameter String permission) {
+ try {
+ sTestApp.pkg().grantPermission(TestApis.users().instrumented(), permission);
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, PERMISSION_GRANT_STATE_DENIED);
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, PERMISSION_GRANT_STATE_DENIED);
- assertWithMessage("Permission should not be denied but was").that(
- sTestApp.pkg().hasPermission(permission)).isTrue();
- } finally {
- sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
- }
+ assertWithMessage("Permission should not be denied but was").that(
+ sTestApp.pkg().hasPermission(permission)).isTrue();
+ } finally {
+ sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
}
}
@Test
@NegativePolicyTest(policy = SetPermissionGrantState.class)
- public void grantPermission_doesNotApply_permissionIsNotGranted() {
- // TODO(b/188893663): Replace with parameterization
- for (String permission : DENYABLE_PERMISSIONS) {
- try {
- sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
+ public void grantPermission_doesNotApply_permissionIsNotGranted(
+ @DeniablePermissionTestParameter String permission) {
+ try {
+ sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, PERMISSION_GRANT_STATE_GRANTED);
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, PERMISSION_GRANT_STATE_GRANTED);
- assertWithMessage("Permission should not be granted but was").that(
- sTestApp.pkg().hasPermission(permission)).isFalse();
- } finally {
- sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
- }
+ assertWithMessage("Permission should not be granted but was").that(
+ sTestApp.pkg().hasPermission(permission)).isFalse();
+ } finally {
+ sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
}
}
@@ -442,37 +449,34 @@
@Test
@CanSetPolicyTest(policy = SetSensorPermissionGranted.class)
@Ignore("TODO(198280344): Re-enable when we can set sensor permissions using device owner")
- public void grantSensorPermission_setsGrantState() {
- // TODO(b/188893663): Replace with parameterization
- for (String permission : SENSOR_PERMISSIONS) {
- int existingGrantState = sDeviceState.dpc().devicePolicyManager()
- .getPermissionGrantState(sDeviceState.dpc().componentName(),
- sTestApp.packageName(), permission);
- try {
- boolean wasSet =
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, PERMISSION_GRANT_STATE_GRANTED);
+ public void grantSensorPermission_setsGrantState(
+ @SensorPermissionTestParameter String permission) {
+ int existingGrantState = sDeviceState.dpc().devicePolicyManager()
+ .getPermissionGrantState(sDeviceState.dpc().componentName(),
+ sTestApp.packageName(), permission);
+ try {
+ boolean wasSet =
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, PERMISSION_GRANT_STATE_GRANTED);
- assertWithMessage("setPermissionGrantState did not return true")
- .that(wasSet).isTrue();
- assertWithMessage("Permission grant state should be set to granted but was not")
- .that(sDeviceState.dpc().devicePolicyManager().getPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission))
- .isEqualTo(PERMISSION_GRANT_STATE_GRANTED);
- } finally {
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, existingGrantState);
- }
+ assertWithMessage("setPermissionGrantState did not return true")
+ .that(wasSet).isTrue();
+ assertWithMessage("Permission grant state should be set to granted but was not")
+ .that(sDeviceState.dpc().devicePolicyManager().getPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission))
+ .isEqualTo(PERMISSION_GRANT_STATE_GRANTED);
+ } finally {
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, existingGrantState);
}
}
@Test
@PositivePolicyTest(policy = SetSmsPermissionGranted.class)
public void grantSmsPermission_permissionIsGranted() {
- // TODO(b/188893663): Replace with parameterization
int existingGrantState = sDeviceState.dpc().devicePolicyManager()
.getPermissionGrantState(sDeviceState.dpc().componentName(),
sTestApp.packageName(), READ_SMS);
@@ -493,24 +497,22 @@
@Test
@PositivePolicyTest(policy = SetSensorPermissionGranted.class)
@Ignore("TODO(198280344): Re-enable when we can set sensor permissions using device owner")
- public void grantSensorPermission_permissionIsGranted() {
- // TODO(b/188893663): Replace with parameterization
- for (String permission : SENSOR_PERMISSIONS) {
- int existingGrantState = sDeviceState.dpc().devicePolicyManager()
- .getPermissionGrantState(sDeviceState.dpc().componentName(),
- sTestApp.packageName(), permission);
- try {
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, PERMISSION_GRANT_STATE_GRANTED);
+ public void grantSensorPermission_permissionIsGranted(
+ @SensorPermissionTestParameter String permission) {
+ int existingGrantState = sDeviceState.dpc().devicePolicyManager()
+ .getPermissionGrantState(sDeviceState.dpc().componentName(),
+ sTestApp.packageName(), permission);
+ try {
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, PERMISSION_GRANT_STATE_GRANTED);
- assertWithMessage("Permission should be granted but was not").that(
- sTestApp.pkg().hasPermission(permission)).isTrue();
- } finally {
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, existingGrantState);
- }
+ assertWithMessage("Permission should be granted but was not").that(
+ sTestApp.pkg().hasPermission(permission)).isTrue();
+ } finally {
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, existingGrantState);
}
}
@@ -537,24 +539,22 @@
@Test
@NegativePolicyTest(policy = SetSensorPermissionGranted.class)
@Ignore("TODO(198280344): Re-enable when we can set sensor permissions using device owner")
- public void grantSensorPermission_doesNotApplyToUser_permissionIsNotGranted() {
- // TODO(b/188893663): Replace with parameterization
- for (String permission : SENSOR_PERMISSIONS) {
- int existingGrantState = sDeviceState.dpc().devicePolicyManager()
- .getPermissionGrantState(sDeviceState.dpc().componentName(),
- sTestApp.packageName(), permission);
- try {
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, PERMISSION_GRANT_STATE_GRANTED);
+ public void grantSensorPermission_doesNotApplyToUser_permissionIsNotGranted(
+ @SensorPermissionTestParameter String permission) {
+ int existingGrantState = sDeviceState.dpc().devicePolicyManager()
+ .getPermissionGrantState(sDeviceState.dpc().componentName(),
+ sTestApp.packageName(), permission);
+ try {
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, PERMISSION_GRANT_STATE_GRANTED);
- assertWithMessage("Permission should not be granted but was").that(
- sTestApp.pkg().hasPermission(permission)).isFalse();
- } finally {
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, existingGrantState);
- }
+ assertWithMessage("Permission should not be granted but was").that(
+ sTestApp.pkg().hasPermission(permission)).isFalse();
+ } finally {
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, existingGrantState);
}
}
@@ -595,30 +595,28 @@
@Test
@CannotSetPolicyTest(policy = SetSensorPermissionGranted.class)
- public void grantSensorPermission_cannotBeApplied_returnsTrueButDoesNotSetGrantState() {
- // TODO(b/188893663): Replace with parameterization
- for (String permission : SENSOR_PERMISSIONS) {
- int existingGrantState = sDeviceState.dpc().devicePolicyManager()
- .getPermissionGrantState(sDeviceState.dpc().componentName(),
- sTestApp.packageName(), permission);
- try {
- boolean wasSet =
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, PERMISSION_GRANT_STATE_GRANTED);
+ public void grantSensorPermission_cannotBeApplied_returnsTrueButDoesNotSetGrantState(
+ @SensorPermissionTestParameter String permission) {
+ int existingGrantState = sDeviceState.dpc().devicePolicyManager()
+ .getPermissionGrantState(sDeviceState.dpc().componentName(),
+ sTestApp.packageName(), permission);
+ try {
+ boolean wasSet =
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, PERMISSION_GRANT_STATE_GRANTED);
- assertWithMessage("setPermissionGrantState did not return true")
- .that(wasSet).isTrue();
- assertWithMessage("Permission grant state should not be set to granted but was")
- .that(sDeviceState.dpc().devicePolicyManager().getPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission))
- .isNotEqualTo(PERMISSION_GRANT_STATE_GRANTED);
- } finally {
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, existingGrantState);
- }
+ assertWithMessage("setPermissionGrantState did not return true")
+ .that(wasSet).isTrue();
+ assertWithMessage("Permission grant state should not be set to granted but was")
+ .that(sDeviceState.dpc().devicePolicyManager().getPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission))
+ .isNotEqualTo(PERMISSION_GRANT_STATE_GRANTED);
+ } finally {
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, existingGrantState);
}
}
@@ -626,30 +624,28 @@
@PositivePolicyTest(policy = SetSensorPermissionGranted.class)
@NotificationsTest
@Ignore("TODO(198280344): Re-enable when we can set sensor permissions using device owner")
- public void grantLocationPermission_userNotified() throws Exception {
- // TODO(b/188893663): Replace with parameterization
- for (String permission : LOCATION_PERMISSIONS) {
- int existingGrantState = sDeviceState.dpc().devicePolicyManager()
- .getPermissionGrantState(sDeviceState.dpc().componentName(),
- sTestApp.packageName(), permission);
+ public void grantLocationPermission_userNotified(
+ @LocationPermissionTestParameter String permission) throws Exception {
+ int existingGrantState = sDeviceState.dpc().devicePolicyManager()
+ .getPermissionGrantState(sDeviceState.dpc().componentName(),
+ sTestApp.packageName(), permission);
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, PERMISSION_GRANT_STATE_DEFAULT);
+ try (NotificationListener notifications = TestApis.notifications().createListener()) {
sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, PERMISSION_GRANT_STATE_DEFAULT);
- try (NotificationListener notifications = TestApis.notifications().createListener()) {
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, PERMISSION_GRANT_STATE_GRANTED);
+ permission, PERMISSION_GRANT_STATE_GRANTED);
- assertThat(notifications.query()
- .wherePackageName().isEqualTo(PERMISSION_CONTROLLER_PACKAGE_NAME)
- .whereNotification().channelId().isEqualTo(
- AUTO_GRANTED_PERMISSIONS_CHANNEL_ID)
- ).wasPosted();
- } finally {
- sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
- sDeviceState.dpc().componentName(), sTestApp.packageName(),
- permission, existingGrantState);
- }
+ assertThat(notifications.query()
+ .wherePackageName().isEqualTo(PERMISSION_CONTROLLER_PACKAGE_NAME)
+ .whereNotification().channelId().isEqualTo(
+ AUTO_GRANTED_PERMISSIONS_CHANNEL_ID)
+ ).wasPosted();
+ } finally {
+ sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+ sDeviceState.dpc().componentName(), sTestApp.packageName(),
+ permission, existingGrantState);
}
}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/ScreenCaptureDisabledTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/ScreenCaptureDisabledTest.java
index c6558e8..6f21edc 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/ScreenCaptureDisabledTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/ScreenCaptureDisabledTest.java
@@ -33,6 +33,7 @@
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureScreenIsOn;
import com.android.bedstead.harrier.annotations.Postsubmit;
import com.android.bedstead.harrier.annotations.SlowApiTest;
import com.android.bedstead.harrier.annotations.enterprise.CanSetPolicyTest;
@@ -74,7 +75,7 @@
public void setUp() {
mAdmin = sDeviceState.dpc().componentName();
mDevicePolicyManager = sDeviceState.dpc().devicePolicyManager();
- //TODO(b/198593716) : Use TestApi to take screnshot instead of UiAutomation.
+ //TODO(b/198593716) : Use TestApi to take screenshot instead of UiAutomation.
mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
mLocalDevicePolicyManager = ApplicationProvider.getApplicationContext().getSystemService(
DevicePolicyManager.class);
@@ -133,6 +134,7 @@
@Test
@NegativePolicyTest(policy = ScreenCaptureDisabled.class)
@Postsubmit(reason = "new test")
+ @EnsureScreenIsOn
public void setScreenCaptureDisabled_true_screenCaptureWorks() {
mDevicePolicyManager.setScreenCaptureDisabled(mAdmin, true);
@@ -143,6 +145,7 @@
@PositivePolicyTest(policy = ScreenCaptureDisabled.class)
@Postsubmit(reason = "new test")
@SlowApiTest("Screenshot policy can take minutes to propagate")
+ @EnsureScreenIsOn
public void setScreenCaptureDisabled_true_screenCaptureFails() {
mDevicePolicyManager.setScreenCaptureDisabled(mAdmin, true);
@@ -152,6 +155,7 @@
@Test
@PositivePolicyTest(policy = ScreenCaptureDisabled.class)
@Postsubmit(reason = "new test")
+ @EnsureScreenIsOn
public void setScreenCaptureDisabled_false_screenCaptureWorks() {
mDevicePolicyManager.setScreenCaptureDisabled(mAdmin, false);
diff --git a/tests/framework/base/OWNERS b/tests/framework/base/OWNERS
index f204dab..ad4b98f 100644
--- a/tests/framework/base/OWNERS
+++ b/tests/framework/base/OWNERS
@@ -13,3 +13,9 @@
# Suggestions
tmfang@google.com
+
+# Locale
+pratyushmore@google.com
+goldmanj@google.com
+
+# Locale
\ No newline at end of file
diff --git a/tests/framework/base/locale/Android.bp b/tests/framework/base/locale/Android.bp
new file mode 100644
index 0000000..9ef1c9f
--- /dev/null
+++ b/tests/framework/base/locale/Android.bp
@@ -0,0 +1,50 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsLocaleManagerTestCases",
+ defaults: ["cts_defaults"],
+
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "junit",
+ "cts-wm-util",
+ "cts-locale-util",
+ ],
+
+ srcs: [
+ "InstallerApp/**/*.java",
+ "TestApp/**/*.java",
+ "src/**/*.java",
+ ],
+
+ sdk_version: "test_current",
+}
diff --git a/tests/framework/base/locale/AndroidManifest.xml b/tests/framework/base/locale/AndroidManifest.xml
new file mode 100644
index 0000000..6839e33
--- /dev/null
+++ b/tests/framework/base/locale/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.localemanager.cts">
+
+ <!-- The permissions below would be needed if tests were not using "adopt shell permissions" to
+ obtain the necessary privileged permissions. -->
+ <!-- uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /-->
+ <!-- uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" /-->
+
+ <eat-comment/>
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.localemanager.cts"
+ android:label="CTS tests for android.localemanager">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener"/>
+ </instrumentation>
+
+</manifest>
+
diff --git a/tests/framework/base/locale/AndroidTest.xml b/tests/framework/base/locale/AndroidTest.xml
new file mode 100644
index 0000000..59845c5
--- /dev/null
+++ b/tests/framework/base/locale/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 Locale Manager test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <!-- LocaleManager APIs should work the same in any user. -->
+ <!-- While running with atest use 'user-type secondary_user'-->
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+ <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="CtsLocaleManagerTestCases.apk" />
+ <option name="test-file-name" value="CtsLocaleTestApp.apk" />
+ <option name="test-file-name" value="CtsLocaleInstallerApp.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.localemanager.cts" />
+ <option name="hidden-api-checks" value="false" />
+ </test>
+</configuration>
diff --git a/tests/framework/base/locale/InstallerApp/Android.bp b/tests/framework/base/locale/InstallerApp/Android.bp
new file mode 100644
index 0000000..c4ba522
--- /dev/null
+++ b/tests/framework/base/locale/InstallerApp/Android.bp
@@ -0,0 +1,41 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsLocaleInstallerApp",
+ defaults: ["cts_defaults"],
+
+ static_libs: [
+ "cts-locale-util",
+ ],
+
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ srcs: ["src/**/*.java"],
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+
+ sdk_version: "system_current",
+}
diff --git a/tests/framework/base/locale/InstallerApp/AndroidManifest.xml b/tests/framework/base/locale/InstallerApp/AndroidManifest.xml
new file mode 100644
index 0000000..67f2d22
--- /dev/null
+++ b/tests/framework/base/locale/InstallerApp/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.localemanager.cts.installer">
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name=".MainActivity"
+ android:label="MainActivity"
+ android:exported="true" >
+ <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.localemanager.cts.installer.InstallerBroadcastReceiver" android:exported="true" android:enabled="true">
+ <intent-filter>
+ <action android:name="android.intent.action.APPLICATION_LOCALE_CHANGED"/>
+ <action android:name="any.action"/>
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/framework/base/locale/InstallerApp/src/android/localemanager/cts/installer/InstallerBroadcastReceiver.java b/tests/framework/base/locale/InstallerApp/src/android/localemanager/cts/installer/InstallerBroadcastReceiver.java
new file mode 100644
index 0000000..98c5ab1
--- /dev/null
+++ b/tests/framework/base/locale/InstallerApp/src/android/localemanager/cts/installer/InstallerBroadcastReceiver.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.localemanager.cts.installer;
+
+import static android.localemanager.cts.util.LocaleConstants.INSTALLER_APP_BROADCAST_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleUtils.constructResultIntent;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * A broadcast receiver that listens to
+ * {@link android.content.Intent#ACTION_APPLICATION_LOCALE_CHANGED}
+ * when it is the installer of the app whose locale has changed.
+ */
+public class InstallerBroadcastReceiver extends BroadcastReceiver{
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Upon successful receipt of broadcast, send back the response to the test which
+ // invoked the change, so that it can verify correctness.
+ if (Intent.ACTION_APPLICATION_LOCALE_CHANGED.equals(intent.getAction())) {
+ context.sendBroadcast(constructResultIntent(
+ INSTALLER_APP_BROADCAST_INFO_PROVIDER_ACTION, intent));
+ }
+ }
+}
diff --git a/tests/framework/base/locale/InstallerApp/src/android/localemanager/cts/installer/MainActivity.java b/tests/framework/base/locale/InstallerApp/src/android/localemanager/cts/installer/MainActivity.java
new file mode 100644
index 0000000..9e25206
--- /dev/null
+++ b/tests/framework/base/locale/InstallerApp/src/android/localemanager/cts/installer/MainActivity.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.localemanager.cts.installer;
+
+import static android.localemanager.cts.util.LocaleConstants.EXTRA_QUERY_LOCALES;
+import static android.localemanager.cts.util.LocaleConstants.INSTALLER_APP_CREATION_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleUtils.constructResultIntent;
+
+import android.app.Activity;
+import android.app.LocaleManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.LocaleList;
+
+import androidx.annotation.Nullable;
+
+/**
+ * An activity used by {@link LocaleManagerTests} to query locales for a given package
+ * when this app is set as the installer of the given package.
+ */
+public class MainActivity extends Activity {
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ if (intent != null && intent.hasExtra(EXTRA_QUERY_LOCALES)) {
+ LocaleManager localeManager = getSystemService(LocaleManager.class);
+ String packageName = intent.getStringExtra(EXTRA_QUERY_LOCALES);
+ LocaleList locales = localeManager.getApplicationLocales(packageName);
+ sendBroadcast(constructResultIntent(INSTALLER_APP_CREATION_INFO_PROVIDER_ACTION,
+ packageName, locales));
+ finish();
+ }
+ }
+}
diff --git a/tests/framework/base/locale/OWNERS b/tests/framework/base/locale/OWNERS
new file mode 100644
index 0000000..694a4ac
--- /dev/null
+++ b/tests/framework/base/locale/OWNERS
@@ -0,0 +1,3 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=1082762&template=1601534
+pratyushmore@google.com
+goldmanj@google.com
\ No newline at end of file
diff --git a/tests/framework/base/locale/TEST_MAPPING b/tests/framework/base/locale/TEST_MAPPING
new file mode 100644
index 0000000..7dd5884
--- /dev/null
+++ b/tests/framework/base/locale/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsLocaleManagerTestCases"
+ }
+ ]
+}
diff --git a/tests/framework/base/locale/TestApp/Android.bp b/tests/framework/base/locale/TestApp/Android.bp
new file mode 100644
index 0000000..db1c0bf
--- /dev/null
+++ b/tests/framework/base/locale/TestApp/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsLocaleTestApp",
+ defaults: ["cts_defaults"],
+
+ static_libs: [
+ "cts-locale-util",
+ ],
+
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ srcs: ["src/**/*.java"],
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+
+ sdk_version: "system_current",
+
+}
diff --git a/tests/framework/base/locale/TestApp/AndroidManifest.xml b/tests/framework/base/locale/TestApp/AndroidManifest.xml
new file mode 100644
index 0000000..cc3f17a
--- /dev/null
+++ b/tests/framework/base/locale/TestApp/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.localemanager.cts.app">
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+
+ <activity android:name=".MainActivity"
+ android:label="MainActivity"
+ android:exported="true"
+ android:configChanges="locale|layoutDirection|screenLayout">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="any.action"/>
+ </intent-filter>
+ </activity>
+
+ <receiver android:name="android.localemanager.cts.app.TestBroadcastReceiver" android:exported="true" android:enabled="true">
+ <intent-filter>
+ <action android:name="android.intent.action.LOCALE_CHANGED"/>
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/framework/base/locale/TestApp/src/android/localemanager/cts/app/MainActivity.java b/tests/framework/base/locale/TestApp/src/android/localemanager/cts/app/MainActivity.java
new file mode 100644
index 0000000..5182515
--- /dev/null
+++ b/tests/framework/base/locale/TestApp/src/android/localemanager/cts/app/MainActivity.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.localemanager.cts.app;
+
+import static android.localemanager.cts.util.LocaleConstants.EXTRA_QUERY_LOCALES;
+import static android.localemanager.cts.util.LocaleConstants.EXTRA_SET_LOCALES;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_CONFIG_CHANGED_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_CREATION_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_PACKAGE;
+import static android.localemanager.cts.util.LocaleUtils.constructResultIntent;
+
+import android.app.Activity;
+import android.app.LocaleManager;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.LocaleList;
+
+import androidx.annotation.Nullable;
+
+/**
+ * This app is used as an external package to test system api
+ * {@link LocaleManager#setApplicationLocales(String, LocaleList)}
+ *
+ * <p> This activity is invoked by the {@link LocaleManagerTests} for below reasons:
+ * <ul>
+ * <li> To keep the app in the foreground/background.
+ * <li> To query locales in the running activity on app restart.
+ * </ul>
+ */
+public class MainActivity extends Activity {
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ if (intent != null && intent.hasExtra(EXTRA_QUERY_LOCALES)) {
+ // This intent extra is sent by app for restarting the app.
+ // Upon app restart, we want to check that the correct locales are received.
+ // So fetch the locales and send them to the calling test for verification.
+ LocaleManager localeManager = getSystemService(LocaleManager.class);
+ LocaleList locales = localeManager.getApplicationLocales();
+
+ // Send back the response with package name of this app and the locales fetched
+ // in current activity to the calling test.
+ sendBroadcast(constructResultIntent(TEST_APP_CREATION_INFO_PROVIDER_ACTION,
+ TEST_APP_PACKAGE, locales));
+ finish();
+ } else if (intent != null && intent.hasExtra(EXTRA_SET_LOCALES)) {
+ // The invoking test directed us to set our application locales to the specified value
+ LocaleManager localeManager = getSystemService(LocaleManager.class);
+ localeManager.setApplicationLocales(LocaleList.forLanguageTags(
+ intent.getStringExtra(EXTRA_SET_LOCALES)));
+ finish();
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ LocaleList locales = newConfig.getLocales();
+
+ // Send back the received locales to the test for correctness assertion
+ sendBroadcast(constructResultIntent(TEST_APP_CONFIG_CHANGED_INFO_PROVIDER_ACTION,
+ TEST_APP_PACKAGE, locales));
+ finish();
+ }
+}
diff --git a/tests/framework/base/locale/TestApp/src/android/localemanager/cts/app/TestBroadcastReceiver.java b/tests/framework/base/locale/TestApp/src/android/localemanager/cts/app/TestBroadcastReceiver.java
new file mode 100644
index 0000000..a68d3af
--- /dev/null
+++ b/tests/framework/base/locale/TestApp/src/android/localemanager/cts/app/TestBroadcastReceiver.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.localemanager.cts.app;
+
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_BROADCAST_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleUtils.constructResultIntent;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * A broadcast receiver that listens to {@link android.content.Intent#ACTION_LOCALE_CHANGED}
+ * when the locale for this app is changed by the test.
+ */
+public class TestBroadcastReceiver extends BroadcastReceiver{
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ // Upon successful receipt of broadcast, send back the response to the test which invoked
+ // the change, so that it can verify correctness.
+ if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
+ context.sendBroadcast(constructResultIntent(TEST_APP_BROADCAST_INFO_PROVIDER_ACTION,
+ intent));
+ }
+ }
+}
diff --git a/tests/framework/base/locale/src/android/localemanager/cts/BlockingBroadcastReceiver.java b/tests/framework/base/locale/src/android/localemanager/cts/BlockingBroadcastReceiver.java
new file mode 100644
index 0000000..ee63fe2
--- /dev/null
+++ b/tests/framework/base/locale/src/android/localemanager/cts/BlockingBroadcastReceiver.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.localemanager.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.LocaleList;
+
+import org.junit.Assert;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Used to dynamically register a receiver in instrumentation test.
+ *
+ * <p>This is used to listen to the following:
+ * <ul>
+ * <li> Broadcasts sent to the current app instrumenting the test. The broadcast is sent by the
+ * service being tested.
+ * <li> Response sent by other apps(TestApp, InstallerApp) to the tests.
+ * </ul>
+ */
+public class BlockingBroadcastReceiver extends BroadcastReceiver {
+ private CountDownLatch mLatch = new CountDownLatch(1);
+ private String mPackageName;
+ private LocaleList mLocales;
+ private int mCalls;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.hasExtra(Intent.EXTRA_PACKAGE_NAME)) {
+ mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ }
+ if (intent.hasExtra(Intent.EXTRA_LOCALE_LIST)) {
+ mLocales = intent.getParcelableExtra(Intent.EXTRA_LOCALE_LIST);
+ }
+ mCalls += 1;
+ mLatch.countDown();
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public LocaleList getLocales() {
+ return mLocales;
+ }
+
+ public void await() throws Exception {
+ mLatch.await(5, TimeUnit.SECONDS);
+ }
+
+ public void reset() {
+ mLatch = new CountDownLatch(1);
+ mCalls = 0;
+ mPackageName = null;
+ mLocales = null;
+ }
+
+ /**
+ * Waits for a while and checks no broadcasts are received.
+ */
+ public void assertNoBroadcastReceived() throws Exception {
+ await();
+ Assert.assertEquals(0, mCalls);
+ }
+}
diff --git a/tests/framework/base/locale/src/android/localemanager/cts/LocaleManagerTests.java b/tests/framework/base/locale/src/android/localemanager/cts/LocaleManagerTests.java
new file mode 100644
index 0000000..3edcb16
--- /dev/null
+++ b/tests/framework/base/locale/src/android/localemanager/cts/LocaleManagerTests.java
@@ -0,0 +1,538 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.localemanager.cts;
+
+import static android.localemanager.cts.util.LocaleConstants.CALLING_PACKAGE;
+import static android.localemanager.cts.util.LocaleConstants.DEFAULT_APP_LOCALES;
+import static android.localemanager.cts.util.LocaleConstants.DEFAULT_SYSTEM_LOCALES;
+import static android.localemanager.cts.util.LocaleConstants.EXTRA_QUERY_LOCALES;
+import static android.localemanager.cts.util.LocaleConstants.EXTRA_SET_LOCALES;
+import static android.localemanager.cts.util.LocaleConstants.INSTALLER_APP_BROADCAST_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleConstants.INSTALLER_APP_BROADCAST_RECEIVER;
+import static android.localemanager.cts.util.LocaleConstants.INSTALLER_APP_CREATION_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleConstants.INSTALLER_APP_MAIN_ACTIVITY;
+import static android.localemanager.cts.util.LocaleConstants.INSTALLER_PACKAGE;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_BROADCAST_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_BROADCAST_RECEIVER;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_CONFIG_CHANGED_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_CREATION_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_MAIN_ACTIVITY;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_PACKAGE;
+import static android.server.wm.CliIntentExtra.extraString;
+
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.LocaleManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.LocaleList;
+import android.server.wm.ActivityManagerTestBase;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.AmUtils;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link android.app.LocaleManager} API(s).
+ *
+ * Build/Install/Run: atest CtsLocaleManagerTestCases
+ */
+@RunWith(AndroidJUnit4.class)
+public class LocaleManagerTests extends ActivityManagerTestBase {
+ private Context mContext;
+ private LocaleManager mLocaleManager;
+
+ /* System locales that were set on the device prior to running tests */
+ private LocaleList mPreviousSystemLocales;
+
+ /* Receiver to listen to the broadcast in the calling (instrumentation) app. */
+ private BlockingBroadcastReceiver mCallingAppBroadcastReceiver;
+
+ /* Receiver to listen to the response from the test app's broadcast receiver. */
+ private BlockingBroadcastReceiver mTestAppBroadcastInfoProvider;
+
+ /* Receiver to listen to the response from the test app's activity. */
+ private BlockingBroadcastReceiver mTestAppCreationInfoProvider;
+
+ /* Receiver to listen to the response from the test app's onConfigChanged method. */
+ private BlockingBroadcastReceiver mTestAppConfigChangedInfoProvider;
+
+ /* Receiver to listen to the response from the installer app. */
+ private BlockingBroadcastReceiver mInstallerBroadcastInfoProvider;
+
+ /* Receiver to listen to the response from the installer app's activity. */
+ private BlockingBroadcastReceiver mInstallerAppCreationInfoProvider;
+
+ @Before
+ public void setUp() throws Exception {
+ // Unlocks the device if locked, since we have tests where the app/activity needs
+ // to be in the foreground/background.
+ super.setUp();
+
+ mContext = InstrumentationRegistry.getTargetContext();
+ mLocaleManager = mContext.getSystemService(LocaleManager.class);
+
+ // Set custom system locales for these tests.
+ // Store the existing system locales and reset back to it in tearDown.
+ mPreviousSystemLocales = mLocaleManager.getSystemLocales();
+ runWithShellPermissionIdentity(() ->
+ mLocaleManager.setSystemLocales(DEFAULT_SYSTEM_LOCALES),
+ Manifest.permission.CHANGE_CONFIGURATION, Manifest.permission.WRITE_SETTINGS);
+
+ resetAppLocalesAsEmpty();
+ AmUtils.waitForBroadcastIdle();
+
+ mCallingAppBroadcastReceiver = new BlockingBroadcastReceiver();
+ mTestAppBroadcastInfoProvider = new BlockingBroadcastReceiver();
+ mInstallerBroadcastInfoProvider = new BlockingBroadcastReceiver();
+ mTestAppCreationInfoProvider = new BlockingBroadcastReceiver();
+ mTestAppConfigChangedInfoProvider = new BlockingBroadcastReceiver();
+ mInstallerAppCreationInfoProvider = new BlockingBroadcastReceiver();
+
+ mContext.registerReceiver(mCallingAppBroadcastReceiver,
+ new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
+ mContext.registerReceiver(mTestAppBroadcastInfoProvider,
+ new IntentFilter(TEST_APP_BROADCAST_INFO_PROVIDER_ACTION));
+ mContext.registerReceiver(mInstallerBroadcastInfoProvider,
+ new IntentFilter(INSTALLER_APP_BROADCAST_INFO_PROVIDER_ACTION));
+ mContext.registerReceiver(mTestAppCreationInfoProvider,
+ new IntentFilter(TEST_APP_CREATION_INFO_PROVIDER_ACTION));
+ mContext.registerReceiver(mTestAppConfigChangedInfoProvider,
+ new IntentFilter(TEST_APP_CONFIG_CHANGED_INFO_PROVIDER_ACTION));
+ mContext.registerReceiver(mInstallerAppCreationInfoProvider,
+ new IntentFilter(INSTALLER_APP_CREATION_INFO_PROVIDER_ACTION));
+
+ setInstallerForPackage(CALLING_PACKAGE);
+ setInstallerForPackage(TEST_APP_PACKAGE);
+
+ bindToBroadcastReceiverOfApp(TEST_APP_PACKAGE, TEST_APP_BROADCAST_RECEIVER);
+ bindToBroadcastReceiverOfApp(INSTALLER_PACKAGE, INSTALLER_APP_BROADCAST_RECEIVER);
+
+ resetReceivers();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ unRegisterReceiver(mCallingAppBroadcastReceiver);
+ unRegisterReceiver(mTestAppBroadcastInfoProvider);
+ unRegisterReceiver(mInstallerBroadcastInfoProvider);
+ unRegisterReceiver(mTestAppCreationInfoProvider);
+ unRegisterReceiver(mInstallerAppCreationInfoProvider);
+ runWithShellPermissionIdentity(() ->
+ mLocaleManager.setSystemLocales(mPreviousSystemLocales),
+ Manifest.permission.CHANGE_CONFIGURATION, Manifest.permission.WRITE_SETTINGS);
+ }
+
+ private void unRegisterReceiver(BlockingBroadcastReceiver receiver) {
+ if (receiver != null) {
+ mContext.unregisterReceiver(receiver);
+ receiver = null;
+ }
+ }
+
+ /**
+ * Send broadcast to given app's receiver with flag {@link Intent#FLAG_INCLUDE_STOPPED_PACKAGES}
+ * and wait for it to be received.
+ *
+ * <p/> This is required since app is in stopped state after installation by tradefed,
+ * and the receivers in another apps don't receive the broadcast without
+ * FLAG_INCLUDE_STOPPED_PACKAGES because they have never been opened even once
+ * after installation.
+ * By doing this we make sure that app is un-stopped when the system sends the broadcasts
+ * in the tests.
+ */
+ private void bindToBroadcastReceiverOfApp(String packageName, String broadcastReceiver) {
+ final Intent intent = new Intent()
+ .setComponent(new ComponentName(packageName, broadcastReceiver))
+ .setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+ CountDownLatch latch = new CountDownLatch(1);
+ mContext.sendOrderedBroadcast(
+ intent,
+ null /* receiverPermission */,
+ new BroadcastReceiver() { /* resultReceiver */
+ @Override public void onReceive(Context context, Intent intent) {
+ latch.countDown();
+ }
+ },
+ null /* scheduler */,
+ Activity.RESULT_OK, /* initialCode */
+ null /* initialData */,
+ null /* initialExtras */);
+ try {
+ assertTrue("Timed out waiting for test broadcast to be received",
+ latch.await(5_000, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ throw new IllegalStateException("Interrupted", e);
+ }
+ }
+
+ /**
+ * Sets the installer as {@link #INSTALLER_PACKAGE} for the target package.
+ */
+ private void setInstallerForPackage(String targetPackageName) {
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mContext.getPackageManager(),
+ (pm) -> pm.setInstallerPackageName(targetPackageName, INSTALLER_PACKAGE));
+ }
+
+ /**
+ * Resets the countdown latch in all the receivers.
+ */
+ private void resetReceivers() {
+ mCallingAppBroadcastReceiver.reset();
+ mInstallerBroadcastInfoProvider.reset();
+ mTestAppBroadcastInfoProvider.reset();
+ mTestAppCreationInfoProvider.reset();
+ mTestAppConfigChangedInfoProvider.reset();
+ mInstallerAppCreationInfoProvider.reset();
+ }
+
+ @Test
+ public void testSetApplicationLocales_persistsAndSendsBroadcast() throws Exception {
+ mLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
+
+ assertLocalesCorrectlySetForCallingApp(DEFAULT_APP_LOCALES);
+ mCallingAppBroadcastReceiver.await();
+ assertReceivedBroadcastContains(mCallingAppBroadcastReceiver,
+ CALLING_PACKAGE, DEFAULT_APP_LOCALES);
+
+ mInstallerBroadcastInfoProvider.await();
+ assertReceivedBroadcastContains(mInstallerBroadcastInfoProvider,
+ CALLING_PACKAGE, DEFAULT_APP_LOCALES);
+ }
+
+ @Test
+ public void testSetApplicationLocales_resetToEmptyLocales_persistsAndSendsBroadcast()
+ throws Exception {
+ // First set the locales to non-empty
+ mLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
+ assertLocalesCorrectlySetForCallingApp(DEFAULT_APP_LOCALES);
+ mCallingAppBroadcastReceiver.await();
+ assertReceivedBroadcastContains(mCallingAppBroadcastReceiver,
+ CALLING_PACKAGE, DEFAULT_APP_LOCALES);
+ mCallingAppBroadcastReceiver.reset();
+
+ mLocaleManager.setApplicationLocales(LocaleList.getEmptyLocaleList());
+
+ assertLocalesCorrectlySetForCallingApp(LocaleList.getEmptyLocaleList());
+ mCallingAppBroadcastReceiver.await();
+ assertReceivedBroadcastContains(mCallingAppBroadcastReceiver,
+ CALLING_PACKAGE, LocaleList.getEmptyLocaleList());
+ }
+
+ @Test
+ public void testSetApplicationLocales_sameLocalesEmpty_noBroadcastSent() throws Exception {
+ // At the start of the test, no app-specific locales are set, so just try to set it to
+ // empty again
+ mLocaleManager.setApplicationLocales(LocaleList.getEmptyLocaleList());
+
+ assertLocalesCorrectlySetForCallingApp(LocaleList.getEmptyLocaleList());
+ mCallingAppBroadcastReceiver.assertNoBroadcastReceived();
+ }
+
+ @Test
+ public void testSetApplicationLocales_sameLocalesNonEmpty_noBroadcastSent() throws Exception {
+ // First set the locales to non-empty
+ mLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
+ assertLocalesCorrectlySetForCallingApp(DEFAULT_APP_LOCALES);
+ mCallingAppBroadcastReceiver.await();
+ assertReceivedBroadcastContains(mCallingAppBroadcastReceiver,
+ CALLING_PACKAGE, DEFAULT_APP_LOCALES);
+ mCallingAppBroadcastReceiver.reset();
+
+ // Then reset to the same app-specific locales, and verify no broadcasts are sent
+ mLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
+
+ assertLocalesCorrectlySetForCallingApp(DEFAULT_APP_LOCALES);
+ mCallingAppBroadcastReceiver.assertNoBroadcastReceived();
+ }
+
+ @Test
+ public void testSetApplicationLocales_getDefaultLocaleList_returnsCorrectList()
+ throws Exception {
+ // Fetch the current system locales when there are no app-locales set.
+ LocaleList systemLocales = LocaleList.getDefault();
+ assertEquals(DEFAULT_SYSTEM_LOCALES, systemLocales);
+
+ mLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
+
+ // Wait for a while since LocaleList::getDefault could take a while to
+ // reflect the new app locales.
+ mCallingAppBroadcastReceiver.await();
+ assertEquals(combineLocales(DEFAULT_APP_LOCALES, systemLocales), LocaleList.getDefault());
+ }
+
+ @Test
+ public void testSetApplicationLocales_forAnotherAppInForeground_persistsAndSendsBroadcast()
+ throws Exception {
+ // Bring the TestApp to the foreground by invoking an activity and verify its visibility.
+ launchActivity(TEST_APP_MAIN_ACTIVITY);
+ mWmState.assertVisibility(TEST_APP_MAIN_ACTIVITY, /* visible*/ true);
+
+ runWithShellPermissionIdentity(() ->
+ mLocaleManager.setApplicationLocales(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES),
+ Manifest.permission.CHANGE_CONFIGURATION);
+ assertLocalesCorrectlySetForAnotherApp(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
+
+ mTestAppBroadcastInfoProvider.await();
+ assertReceivedBroadcastContains(mTestAppBroadcastInfoProvider, TEST_APP_PACKAGE,
+ DEFAULT_APP_LOCALES);
+
+ mInstallerBroadcastInfoProvider.await();
+ assertReceivedBroadcastContains(mInstallerBroadcastInfoProvider, TEST_APP_PACKAGE,
+ DEFAULT_APP_LOCALES);
+ }
+
+ @Test
+ public void testSetApplicationLocales_forAnotherAppInBackground_persistsAndSendsBroadcast()
+ throws Exception {
+ // Invoke the app by launching an activity.
+ launchActivity(TEST_APP_MAIN_ACTIVITY);
+ // Send the TestApp to the background.
+ launchHomeActivity();
+ mWmState.waitAndAssertVisibilityGone(TEST_APP_MAIN_ACTIVITY);
+
+ runWithShellPermissionIdentity(() ->
+ mLocaleManager.setApplicationLocales(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES),
+ Manifest.permission.CHANGE_CONFIGURATION);
+
+ assertLocalesCorrectlySetForAnotherApp(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
+
+ mTestAppBroadcastInfoProvider.await();
+ assertReceivedBroadcastContains(mTestAppBroadcastInfoProvider, TEST_APP_PACKAGE,
+ DEFAULT_APP_LOCALES);
+
+ mInstallerBroadcastInfoProvider.await();
+ assertReceivedBroadcastContains(mInstallerBroadcastInfoProvider, TEST_APP_PACKAGE,
+ DEFAULT_APP_LOCALES);
+ }
+
+ @Test
+ public void testSetApplicationLocales_forAnotherApp_persistsOnAppRestart() throws Exception {
+ runWithShellPermissionIdentity(() ->
+ mLocaleManager.setApplicationLocales(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES),
+ Manifest.permission.CHANGE_CONFIGURATION);
+
+ // Re-start the app by starting an activity and check if locales correctly
+ // received by the app and listen to the broadcast for result from the app.
+ launchActivity(TEST_APP_MAIN_ACTIVITY, extraString(EXTRA_QUERY_LOCALES, "true"));
+
+ mTestAppCreationInfoProvider.await();
+ assertReceivedBroadcastContains(mTestAppCreationInfoProvider, TEST_APP_PACKAGE,
+ DEFAULT_APP_LOCALES);
+ }
+
+ @Test
+ public void
+ testSetApplicationLocales_wthoutPermissionforAnotherApp_throwsExceptionAndNoBroadcast()
+ throws Exception {
+ try {
+ mLocaleManager.setApplicationLocales(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
+ } catch (SecurityException e) {
+ // expected as not having appropriate permission to change locales for another app.
+ }
+
+ // Since the locales weren't allowed to persist, no broadcasts should be sent by the system.
+ mTestAppBroadcastInfoProvider.assertNoBroadcastReceived();
+ mInstallerBroadcastInfoProvider.assertNoBroadcastReceived();
+ }
+
+ @Test
+ public void testSetApplicationLocales_forAnotherAppInForeground_callsOnConfigChanged()
+ throws Exception {
+ // Bring the TestApp to the foreground by invoking an activity and verify its visibility.
+ launchActivity(TEST_APP_MAIN_ACTIVITY);
+ mWmState.assertVisibility(TEST_APP_MAIN_ACTIVITY, /* visible*/ true);
+
+ runWithShellPermissionIdentity(() ->
+ mLocaleManager.setApplicationLocales(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES),
+ Manifest.permission.CHANGE_CONFIGURATION);
+ assertLocalesCorrectlySetForAnotherApp(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
+
+ mTestAppConfigChangedInfoProvider.await();
+ assertReceivedBroadcastContains(mTestAppConfigChangedInfoProvider, TEST_APP_PACKAGE,
+ DEFAULT_APP_LOCALES);
+ }
+
+ @Test
+ public void testSetApplicationLocales_withReadAppSpecificLocalesPermission_receivesBroadcast()
+ throws Exception {
+
+ BlockingBroadcastReceiver appSpecificLocaleBroadcastReceiver =
+ new BlockingBroadcastReceiver();
+ mContext.registerReceiver(appSpecificLocaleBroadcastReceiver,
+ new IntentFilter(Intent.ACTION_APPLICATION_LOCALE_CHANGED));
+
+ // Hold Manifest.permission.READ_APP_SPECIFIC_LOCALES while the broadcast is sent,
+ // so that we receive it
+ runWithShellPermissionIdentity(() -> {
+ // Tell the test app to change its app-specific locales
+ launchActivity(TEST_APP_MAIN_ACTIVITY, extraString(EXTRA_SET_LOCALES,
+ DEFAULT_APP_LOCALES.toLanguageTags()));
+ assertLocalesCorrectlySetForAnotherApp(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
+
+ appSpecificLocaleBroadcastReceiver.await();
+ }, Manifest.permission.READ_APP_SPECIFIC_LOCALES);
+
+ assertReceivedBroadcastContains(appSpecificLocaleBroadcastReceiver,
+ TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
+ }
+
+
+ @Test
+ public void testSetApplicationLocales_appWithoutPermission_noBroadcastsReceived()
+ throws Exception {
+
+ BlockingBroadcastReceiver appSpecificLocaleBroadcastReceiver =
+ new BlockingBroadcastReceiver();
+ mContext.registerReceiver(appSpecificLocaleBroadcastReceiver,
+ new IntentFilter(Intent.ACTION_APPLICATION_LOCALE_CHANGED));
+
+ // Tell the test app to change its app-specific locales
+ launchActivity(TEST_APP_MAIN_ACTIVITY, extraString(EXTRA_SET_LOCALES,
+ DEFAULT_APP_LOCALES.toLanguageTags()));
+ assertLocalesCorrectlySetForAnotherApp(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
+
+ // Ensure that no broadcasts were received since the change was for another app (the Test
+ // App) and we are neither the Test App's installer, nor do we hold
+ // Manifest.permission.READ_APP_SPECIFIC_LOCALES
+ appSpecificLocaleBroadcastReceiver.assertNoBroadcastReceived();
+ mCallingAppBroadcastReceiver.assertNoBroadcastReceived();
+ }
+
+ @Test
+ public void testGetApplicationLocales_callerIsInstaller_returnsLocales() throws Exception {
+ // Set locales for calling app
+ mLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
+
+ // Make sure that locales were set for the app.
+ assertLocalesCorrectlySetForCallingApp(DEFAULT_APP_LOCALES);
+ // Tell the installer app to fetch locales for calling package.
+ launchActivity(INSTALLER_APP_MAIN_ACTIVITY,
+ extraString(EXTRA_QUERY_LOCALES, CALLING_PACKAGE));
+
+ mInstallerAppCreationInfoProvider.await();
+ assertReceivedBroadcastContains(mInstallerAppCreationInfoProvider,
+ CALLING_PACKAGE, DEFAULT_APP_LOCALES);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testGetApplicationLocales_withoutPermissionforAnotherApp_throwsException()
+ throws Exception {
+ mLocaleManager.getApplicationLocales(TEST_APP_PACKAGE);
+ fail("Expected SecurityException due to not having appropriate permission "
+ + "for querying locales of another app.");
+ }
+
+ @Test
+ public void testGetApplicationLocales_noAppLocalesSet_returnsEmptyList() {
+ // When app-specific locales aren't set, we expect get api to return empty list
+ // and not throw any error.
+ assertEquals(LocaleList.getEmptyLocaleList(), mLocaleManager.getApplicationLocales());
+ }
+
+ /**
+ * Verifies that the locales are correctly set for calling(instrumentation) app
+ * by fetching locales of the app with a binder call.
+ */
+ private void assertLocalesCorrectlySetForCallingApp(LocaleList expectedLocales) {
+ assertEquals(expectedLocales, mLocaleManager.getApplicationLocales());
+ }
+
+ /**
+ * Verifies that the locales are correctly set for another package
+ * by fetching locales of the app with a binder call.
+ */
+ private void assertLocalesCorrectlySetForAnotherApp(String packageName,
+ LocaleList expectedLocales) throws Exception {
+ assertEquals(expectedLocales, getApplicationLocales(packageName));
+ }
+
+ private LocaleList getApplicationLocales(String packageName) throws Exception {
+ return callWithShellPermissionIdentity(() ->
+ mLocaleManager.getApplicationLocales(packageName),
+ Manifest.permission.READ_APP_SPECIFIC_LOCALES);
+ }
+
+ /**
+ * Verifies that the broadcast received in the relevant apps have the correct information
+ * in the intent extras. It verifies the below extras:
+ * <ul>
+ * <li> {@link Intent#EXTRA_PACKAGE_NAME}
+ * <li> {@link Intent#EXTRA_LOCALE_LIST}
+ * </ul>
+ */
+ private void assertReceivedBroadcastContains(BlockingBroadcastReceiver receiver,
+ String expectedPackageName, LocaleList expectedLocales) {
+ assertEquals(expectedPackageName, receiver.getPackageName());
+ assertEquals(expectedLocales, receiver.getLocales());
+ }
+
+ /**
+ * Creates a combined {@link LocaleList} by placing app locales before system locales and
+ * dropping any duplicates.
+ *
+ * We need to combine them since {@link LocaleList} does not have any direct way like
+ * a normal list to check if locale or subset of locales is present.
+ */
+ private LocaleList combineLocales(LocaleList appLocales, LocaleList systemLocales) {
+ Locale[] combinedLocales = new Locale[appLocales.size() + systemLocales.size()];
+ for (int i = 0; i < appLocales.size(); i++) {
+ combinedLocales[i] = appLocales.get(i);
+ }
+ for (int i = 0; i < systemLocales.size(); i++) {
+ combinedLocales[i + appLocales.size()] = systemLocales.get(i);
+ }
+ // Constructor of {@link LocaleList} removes duplicates.
+ return new LocaleList(combinedLocales);
+ }
+
+ /**
+ * Reset the locales for the apps to empty list as they could have been set previously
+ * by some other test.
+ */
+ private void resetAppLocalesAsEmpty() throws Exception {
+ // Reset locales for the calling app.
+ mLocaleManager.setApplicationLocales(LocaleList.getEmptyLocaleList());
+
+ // Reset locales for the TestApp.
+ runWithShellPermissionIdentity(() ->
+ mLocaleManager.setApplicationLocales(TEST_APP_PACKAGE,
+ LocaleList.getEmptyLocaleList()),
+ Manifest.permission.CHANGE_CONFIGURATION);
+ }
+}
diff --git a/tests/framework/base/locale/util/Android.bp b/tests/framework/base/locale/util/Android.bp
new file mode 100644
index 0000000..979454d
--- /dev/null
+++ b/tests/framework/base/locale/util/Android.bp
@@ -0,0 +1,29 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_helper_library {
+ name: "cts-locale-util",
+
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ static_libs: [
+ "androidx.test.rules",
+ ],
+}
diff --git a/tests/framework/base/locale/util/src/android/localemanager/cts/util/LocaleConstants.java b/tests/framework/base/locale/util/src/android/localemanager/cts/util/LocaleConstants.java
new file mode 100644
index 0000000..1c79f04
--- /dev/null
+++ b/tests/framework/base/locale/util/src/android/localemanager/cts/util/LocaleConstants.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.localemanager.cts.util;
+
+import android.content.ComponentName;
+import android.os.LocaleList;
+
+/**
+ * Common constants used across {@link android.localemanager.cts.LocaleManagerTests}
+ * and the test apps.
+ */
+public final class LocaleConstants {
+
+ private LocaleConstants() {}
+
+ public static final LocaleList DEFAULT_SYSTEM_LOCALES =
+ LocaleList.forLanguageTags("en_US,fr_FR");
+
+ public static final LocaleList DEFAULT_APP_LOCALES = LocaleList.forLanguageTags("hi,fr_FR");
+
+ public static final String CALLING_PACKAGE = "android.localemanager.cts";
+
+ public static final String TEST_APP_PACKAGE = "android.localemanager.cts.app";
+
+ public static final String INSTALLER_PACKAGE = "android.localemanager.cts.installer";
+
+ public static final ComponentName TEST_APP_MAIN_ACTIVITY = new ComponentName(TEST_APP_PACKAGE,
+ TEST_APP_PACKAGE + ".MainActivity");
+
+ public static final ComponentName INSTALLER_APP_MAIN_ACTIVITY =
+ new ComponentName(INSTALLER_PACKAGE, INSTALLER_PACKAGE + ".MainActivity");
+
+ public static final String TEST_APP_BROADCAST_INFO_PROVIDER_ACTION =
+ "android.locale.cts.action.TEST_APP_BROADCAST_INFO_PROVIDER";
+
+ public static final String TEST_APP_CREATION_INFO_PROVIDER_ACTION =
+ "android.locale.cts.action.TEST_APP_CREATION_INFO_PROVIDER";
+
+ public static final String TEST_APP_CONFIG_CHANGED_INFO_PROVIDER_ACTION =
+ "android.locale.cts.action.TEST_APP_CONFIG_CHANGED_INFO_PROVIDER";
+
+ public static final String INSTALLER_APP_BROADCAST_INFO_PROVIDER_ACTION =
+ "android.locale.cts.action.INSTALLER_APP_BROADCAST_INFO_PROVIDER";
+
+ public static final String INSTALLER_APP_CREATION_INFO_PROVIDER_ACTION =
+ "android.locale.cts.action.INSTALLER_APP_CREATION_INFO_PROVIDER";
+
+ public static final String TEST_APP_BROADCAST_RECEIVER = TEST_APP_PACKAGE
+ + ".TestBroadcastReceiver";
+
+ public static final String INSTALLER_APP_BROADCAST_RECEIVER = INSTALLER_PACKAGE
+ + ".InstallerBroadcastReceiver";
+
+ public static final String EXTRA_QUERY_LOCALES = "query_locales";
+
+ public static final String EXTRA_SET_LOCALES = "set_locales";
+
+}
diff --git a/tests/framework/base/locale/util/src/android/localemanager/cts/util/LocaleUtils.java b/tests/framework/base/locale/util/src/android/localemanager/cts/util/LocaleUtils.java
new file mode 100644
index 0000000..58c556e
--- /dev/null
+++ b/tests/framework/base/locale/util/src/android/localemanager/cts/util/LocaleUtils.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.localemanager.cts.util;
+
+import android.content.Intent;
+import android.os.LocaleList;
+import android.os.Parcelable;
+
+/**
+ * Static utility methods used across the test apps.
+ */
+public final class LocaleUtils {
+
+ private LocaleUtils() {}
+
+ /**
+ * Constructs a new intent with given action. Retrieves information from the passed intent
+ * and puts it in the extras of the new returned intent.
+ *
+ * <p> The passed intent contains package name and the locales. The intent action is expected to
+ * be either of the following types.
+ * <ul>
+ * <li> {@link LocaleConstants#TEST_APP_BROADCAST_INFO_PROVIDER_ACTION}
+ * <li> {@link LocaleConstants#INSTALLER_APP_BROADCAST_INFO_PROVIDER_ACTION}
+ * </ul>
+ *
+ */
+ public static Intent constructResultIntent(String action, Intent intent) {
+ return new Intent(action)
+ .putExtra(Intent.EXTRA_PACKAGE_NAME,
+ intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME))
+ .putExtra(Intent.EXTRA_LOCALE_LIST,
+ (Parcelable) intent.getParcelableExtra(Intent.EXTRA_LOCALE_LIST));
+ }
+
+ /**
+ * Constructs a new intent with given action. Also puts the given package name and the locales
+ * in the extras of the intent.
+ *
+ * <p> The action is expected to be of the type
+ * {@link LocaleConstants#TEST_APP_CREATION_INFO_PROVIDER_ACTION}
+ */
+ public static Intent constructResultIntent(String action, String packageName,
+ LocaleList locales) {
+ return new Intent(action)
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+ .putExtra(Intent.EXTRA_LOCALE_LIST, locales);
+ }
+}
diff --git a/tests/framework/base/localeconfig/Android.bp b/tests/framework/base/localeconfig/Android.bp
new file mode 100644
index 0000000..9e14b8b
--- /dev/null
+++ b/tests/framework/base/localeconfig/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsLocaleConfigTestCases",
+ defaults: ["cts_defaults"],
+
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ static_libs: [
+ "ctstestrunner-axt",
+ "junit",
+ ],
+
+ java_resource_dirs: ["res"],
+
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ sdk_version: "test_current",
+}
+
diff --git a/tests/framework/base/localeconfig/AndroidManifest.xml b/tests/framework/base/localeconfig/AndroidManifest.xml
new file mode 100644
index 0000000..14159b9
--- /dev/null
+++ b/tests/framework/base/localeconfig/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.localeconfig">
+ <application android:debuggable="true"
+ android:localeConfig="@xml/locales_config">
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.localeconfig"
+ android:label="CTS tests for android.app.LocaleConfig">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener"/>
+ </instrumentation>
+</manifest>
diff --git a/tests/framework/base/localeconfig/AndroidTest.xml b/tests/framework/base/localeconfig/AndroidTest.xml
new file mode 100644
index 0000000..31c2b63
--- /dev/null
+++ b/tests/framework/base/localeconfig/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 Locale Config test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsLocaleConfigTestCases.apk" />
+ <option name="test-file-name" value="CtsNoLocaleConfigTagTests.apk" />
+ <option name="test-file-name" value="CtsMalformedInputTests.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.cts.localeconfig" />
+ <option name="hidden-api-checks" value="false" />
+ </test>
+</configuration>
diff --git a/tests/framework/base/localeconfig/OWNERS b/tests/framework/base/localeconfig/OWNERS
new file mode 100644
index 0000000..248236f
--- /dev/null
+++ b/tests/framework/base/localeconfig/OWNERS
@@ -0,0 +1,4 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=28174&template=1650608
+joshhou@google.com
+goldmanj@google.com
+pratyushmore@google.com
diff --git a/tests/framework/base/localeconfig/malformedinput/Android.bp b/tests/framework/base/localeconfig/malformedinput/Android.bp
new file mode 100644
index 0000000..0dbc3e7
--- /dev/null
+++ b/tests/framework/base/localeconfig/malformedinput/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsMalformedInputTests",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "test_current",
+ //resource_dirs: ["helper/res-malformedinput"],
+ srcs: ["src/**/*.java"],
+ static_libs: ["androidx.legacy_legacy-support-v4"],
+ libs: ["android.test.base"],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ certificate: ":cts-testkey1",
+ dex_preopt: {
+ enabled: false,
+ },
+}
\ No newline at end of file
diff --git a/tests/framework/base/localeconfig/malformedinput/AndroidManifest.xml b/tests/framework/base/localeconfig/malformedinput/AndroidManifest.xml
new file mode 100644
index 0000000..84e0486
--- /dev/null
+++ b/tests/framework/base/localeconfig/malformedinput/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.malformedinput">
+ <application android:debuggable="true"
+ android:localeConfig="@xml/locales_config">
+ </application>
+</manifest>
diff --git a/tests/framework/base/localeconfig/malformedinput/res/xml/locales_config.xml b/tests/framework/base/localeconfig/malformedinput/res/xml/locales_config.xml
new file mode 100644
index 0000000..0e77206
--- /dev/null
+++ b/tests/framework/base/localeconfig/malformedinput/res/xml/locales_config.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- the locale-config is misspelled -->
+<locale-confi xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:aapt="http://schemas.android.com/aapt">
+ <locale android:name="en-US"/>
+ <locale android:name="zh-TW"/>
+ <locale android:name="pt"/>
+ <locale android:name="fr"/>
+ <locale android:name="zh-Hans-SG"/>
+</locale-confi>
+
diff --git a/tests/framework/base/localeconfig/nolocaleconfig/Android.bp b/tests/framework/base/localeconfig/nolocaleconfig/Android.bp
new file mode 100644
index 0000000..fcbe4c4
--- /dev/null
+++ b/tests/framework/base/localeconfig/nolocaleconfig/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsNoLocaleConfigTagTests",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "test_current",
+ srcs: ["src/**/*.java"],
+ static_libs: ["androidx.legacy_legacy-support-v4"],
+ libs: ["android.test.base"],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ certificate: ":cts-testkey1",
+ dex_preopt: {
+ enabled: false,
+ },
+}
\ No newline at end of file
diff --git a/tests/framework/base/localeconfig/nolocaleconfig/AndroidManifest.xml b/tests/framework/base/localeconfig/nolocaleconfig/AndroidManifest.xml
new file mode 100644
index 0000000..666f583
--- /dev/null
+++ b/tests/framework/base/localeconfig/nolocaleconfig/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.nolocaleconfigtag">
+ <application android:debuggable="true">
+ </application>
+</manifest>
diff --git a/tests/framework/base/localeconfig/res/xml/locales_config.xml b/tests/framework/base/localeconfig/res/xml/locales_config.xml
new file mode 100644
index 0000000..9d68b32
--- /dev/null
+++ b/tests/framework/base/localeconfig/res/xml/locales_config.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<locale-config xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:aapt="http://schemas.android.com/aapt">
+ <locale android:name="en-US"/>
+ <locale android:name="zh-TW"/>
+ <locale android:name="pt"/>
+ <locale android:name="fr"/>
+ <locale android:name="zh-Hans-SG"/>
+</locale-config>
+
diff --git a/tests/framework/base/localeconfig/src/com/android/cts/localeconfig/LocaleConfigTest.java b/tests/framework/base/localeconfig/src/com/android/cts/localeconfig/LocaleConfigTest.java
new file mode 100644
index 0000000..8ec58fc
--- /dev/null
+++ b/tests/framework/base/localeconfig/src/com/android/cts/localeconfig/LocaleConfigTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.localeconfig;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.app.LocaleConfig;
+import android.content.Context;
+import android.os.LocaleList;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Tests for {@link android.app.LocaleConfig} API(s).
+ *
+ * Build/Install/Run: atest LocaleConfigTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class LocaleConfigTest {
+ private static final String NOTAG_PACKAGE_NAME = "com.android.cts.nolocaleconfigtag";
+ private static final String MALFORMED_INPUT_PACKAGE_NAME = "com.android.cts.malformedinput";
+ private static final List<String> EXPECT_LOCALES = Arrays.asList(
+ new String[]{"en-US", "zh-TW", "pt", "fr", "zh-Hans-SG"});
+
+ @Test
+ public void testGetLocaleList() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+ LocaleConfig localeConfig = new LocaleConfig(context);
+
+ assertEquals(EXPECT_LOCALES.stream()
+ .sorted()
+ .collect(Collectors.toList()),
+ new ArrayList<String>(Arrays.asList(
+ localeConfig.getSupportedLocales().toLanguageTags().split(","))).stream()
+ .sorted()
+ .collect(Collectors.toList()));
+
+ assertEquals(LocaleConfig.STATUS_SUCCESS, localeConfig.getStatus());
+ }
+
+ @Test
+ public void testNoLocaleConfigTag() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+ Context appContext = context.createPackageContext(NOTAG_PACKAGE_NAME, 0);
+ LocaleConfig localeConfig = new LocaleConfig(appContext);
+ LocaleList localeList = localeConfig.getSupportedLocales();
+
+ assertNull(localeList);
+
+ assertEquals(LocaleConfig.STATUS_NOT_SPECIFIED, localeConfig.getStatus());
+ }
+
+ @Test
+ public void testLocaleConfigMalformedInput() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+ Context appContext = context.createPackageContext(MALFORMED_INPUT_PACKAGE_NAME, 0);
+ LocaleConfig localeConfig = new LocaleConfig(appContext);
+ LocaleList localeList = localeConfig.getSupportedLocales();
+
+ assertNull(localeList);
+
+ assertEquals(LocaleConfig.STATUS_PARSING_FAILED, localeConfig.getStatus());
+ }
+}
+
diff --git a/tests/framework/base/windowmanager/app/Android.bp b/tests/framework/base/windowmanager/app/Android.bp
index 94e915d..1288a70 100644
--- a/tests/framework/base/windowmanager/app/Android.bp
+++ b/tests/framework/base/windowmanager/app/Android.bp
@@ -26,8 +26,6 @@
srcs: [
"src/**/*.java",
- // Used by InputMethodTestActivity for ImeAwareEditText.
- ":compatibility-device-util-nodeps",
":CtsVerifierMockVrListenerServiceFiles",
],
diff --git a/tests/framework/base/windowmanager/app/AndroidManifest.xml b/tests/framework/base/windowmanager/app/AndroidManifest.xml
index fbbbbca..abeb1fe 100755
--- a/tests/framework/base/windowmanager/app/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/app/AndroidManifest.xml
@@ -610,6 +610,14 @@
android:exported="true"
android:colorMode="wideColorGamut"
android:theme="@style/BadBlurryDialog"/>
+ <activity android:name=".ClearBackgroundTransitionExitActivity"
+ android:theme="@style/Theme.BackgroundColor"
+ android:exported="true"
+ android:colorMode="wideColorGamut"/>
+ <activity android:name=".ClearBackgroundTransitionEnterActivity"
+ android:theme="@style/Theme.BackgroundColor"
+ android:exported="true"
+ android:colorMode="wideColorGamut"/>
<!-- Splash Screen Test Activities -->
<activity android:name=".HandleSplashScreenExitActivity"
diff --git a/tests/framework/base/windowmanager/app/res/anim/show_background_hide_window_animation.xml b/tests/framework/base/windowmanager/app/res/anim/show_background_hide_window_animation.xml
new file mode 100644
index 0000000..4742a87
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/res/anim/show_background_hide_window_animation.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false"
+ android:showBackground="true">
+
+ <alpha
+ android:fromAlpha="0"
+ android:toAlpha="0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@android:interpolator/linear"
+ android:startOffset="0"
+ android:duration="5000"/>
+</set>
diff --git a/tests/framework/base/windowmanager/app/res/values/styles.xml b/tests/framework/base/windowmanager/app/res/values/styles.xml
index 0c9df63..611bc33 100644
--- a/tests/framework/base/windowmanager/app/res/values/styles.xml
+++ b/tests/framework/base/windowmanager/app/res/values/styles.xml
@@ -92,4 +92,26 @@
<style name="SplashScreenOverrideTheme" parent="ReplaceIconTheme">
<item name="android:windowSplashScreenBackground">@drawable/red</item>
</style>
+
+ <style name="Animation.ClearActivityAnimation" parent="@android:style/Animation.Activity">
+ <item name="android:activityOpenEnterAnimation">
+ @anim/show_background_hide_window_animation
+ </item>
+ <item name="android:activityOpenExitAnimation">
+ @anim/show_background_hide_window_animation
+ </item>
+ <item name="android:activityCloseEnterAnimation">
+ @anim/show_background_hide_window_animation
+ </item>
+ <item name="android:activityCloseExitAnimation">
+ @anim/show_background_hide_window_animation
+ </item>
+ </style>
+
+ <style name="Theme.BackgroundColor" parent="android:Theme.Light">
+ <item name="android:windowAnimationStyle">@style/Animation.ClearActivityAnimation</item>
+ <item name="android:background">#ffffff</item>
+ <item name="android:windowBackground">#ffffff</item>
+ <item name="android:colorBackground">#ffffff</item>
+ </style>
</resources>
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/BroadcastReceiverActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/BroadcastReceiverActivity.java
index 26c5b3a..130c970 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/BroadcastReceiverActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/BroadcastReceiverActivity.java
@@ -108,7 +108,8 @@
}
void register() {
- mAppContext.registerReceiver(this, new IntentFilter(ACTION_TRIGGER_BROADCAST));
+ mAppContext.registerReceiver(this, new IntentFilter(ACTION_TRIGGER_BROADCAST),
+ Context.RECEIVER_EXPORTED);
}
void associate(Activity activity) {
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClearBackgroundTransitionEnterActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClearBackgroundTransitionEnterActivity.java
new file mode 100644
index 0000000..83ab042
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClearBackgroundTransitionEnterActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.app;
+
+import android.app.Activity;
+
+/**
+ * Activity to test that show background for activity transitions works
+ */
+public class ClearBackgroundTransitionEnterActivity extends Activity {
+ @Override
+ protected void onResume() {
+ super.onResume();
+ setContentView(R.layout.background_image);
+ overridePendingTransition(R.anim.show_background_hide_window_animation,
+ R.anim.show_background_hide_window_animation);
+ }
+}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClearBackgroundTransitionExitActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClearBackgroundTransitionExitActivity.java
new file mode 100644
index 0000000..be0efd0
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClearBackgroundTransitionExitActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.app;
+
+import static android.server.wm.app.Components.BackgroundActivityTransition.TRANSITION_REQUESTED;
+import static android.server.wm.app.Components.CLEAR_BACKGROUND_TRANSITION_EXIT_ACTIVITY;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.server.wm.TestJournalProvider;
+
+/**
+ * Activity to test that show background for activity transitions works
+ */
+public class ClearBackgroundTransitionExitActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.background_image);
+
+ // Delay the starting the activity so we don't skip the transition.
+ startActivityDelayed();
+ }
+
+ private void startActivityDelayed() {
+ Runnable r = () -> {
+ // Notify the test journal that we are starting the activity transition
+ TestJournalProvider.putExtras(
+ getBaseContext(), CLEAR_BACKGROUND_TRANSITION_EXIT_ACTIVITY, bundle -> {
+ bundle.putBoolean(TRANSITION_REQUESTED,
+ true);
+ });
+ startActivity(new Intent(
+ ClearBackgroundTransitionExitActivity.this,
+ ClearBackgroundTransitionEnterActivity.class
+ ));
+ };
+
+ Handler h = new Handler();
+ h.postDelayed(r, 1000);
+ }
+}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
index 79499f7..ff70b3d 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
@@ -278,6 +278,12 @@
public static final ComponentName BAD_BLUR_ACTIVITY =
component("BadBlurActivity");
+ public static final ComponentName CLEAR_BACKGROUND_TRANSITION_EXIT_ACTIVITY =
+ component("ClearBackgroundTransitionExitActivity");
+
+ public static final ComponentName CLEAR_BACKGROUND_TRANSITION_ENTER_ACTIVITY =
+ component("ClearBackgroundTransitionExitActivity");
+
/**
* The keys are used for {@link TestJournalProvider} when testing starting window.
*/
@@ -335,6 +341,7 @@
public static final String COMMAND_NAVIGATE_UP_TO = "navigate_up_to";
public static final String COMMAND_START_ACTIVITY = "start_activity";
public static final String COMMAND_START_ACTIVITIES = "start_activities";
+ public static final String EXTRA_OPTION = "option";
}
/**
@@ -638,6 +645,10 @@
public static final String SHOULD_HIDE = "should_hide";
}
+ public static class BackgroundActivityTransition {
+ public static final String TRANSITION_REQUESTED = "transition_requested";
+ }
+
private static ComponentName component(String className) {
return component(Components.class, className);
}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java
index c8e2140..b6be23b 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java
@@ -38,7 +38,6 @@
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.server.wm.TestJournalProvider;
-import android.util.Log;
import android.window.SplashScreen;
public class HandleSplashScreenExitActivity extends Activity {
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java
index 4014999..0be70e1 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java
@@ -37,7 +37,7 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION));
+ registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION), Context.RECEIVER_EXPORTED);
}
BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
index b39b09c..e053322 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
@@ -240,7 +240,7 @@
filter.addAction(ACTION_SET_REQUESTED_ORIENTATION);
filter.addAction(ACTION_FINISH);
filter.addAction(ACTION_ON_PIP_REQUESTED);
- registerReceiver(mReceiver, filter);
+ registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED);
// Don't dump configuration when entering PIP to avoid the verifier getting the intermediate
// state. In this case it is expected that the verifier will check the changed configuration
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java
index 9c66d92..e087647 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java
@@ -31,7 +31,6 @@
import android.os.Bundle;
import android.os.SystemClock;
import android.server.wm.TestJournalProvider;
-import android.util.Log;
import android.view.SurfaceView;
import android.view.View;
import android.window.SplashScreen;
diff --git a/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/EmbeddingActivity.java b/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/EmbeddingActivity.java
index aa16584..409bff9 100644
--- a/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/EmbeddingActivity.java
+++ b/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/EmbeddingActivity.java
@@ -40,7 +40,7 @@
super.onCreate(icicle);
IntentFilter broadcastFilter = new IntentFilter(ACTION_EMBEDDING_TEST_ACTIVITY_START);
- registerReceiver(mBroadcastReceiver, broadcastFilter);
+ registerReceiver(mBroadcastReceiver, broadcastFilter, Context.RECEIVER_EXPORTED);
}
@Override
diff --git a/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/TestActivity.java b/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/TestActivity.java
index 6b30644..2a570ac 100644
--- a/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/TestActivity.java
+++ b/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/TestActivity.java
@@ -17,14 +17,15 @@
package android.server.wm.app;
import static android.server.wm.app.Components.TestActivity.COMMAND_NAVIGATE_UP_TO;
+import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITIES;
import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITY;
import static android.server.wm.app.Components.TestActivity.EXTRA_CONFIG_ASSETS_SEQ;
import static android.server.wm.app.Components.TestActivity.EXTRA_FIXED_ORIENTATION;
-import static android.server.wm.app.Components.TestActivity.EXTRA_INTENTS;
import static android.server.wm.app.Components.TestActivity.EXTRA_INTENT;
+import static android.server.wm.app.Components.TestActivity.EXTRA_INTENTS;
import static android.server.wm.app.Components.TestActivity.EXTRA_NO_IDLE;
+import static android.server.wm.app.Components.TestActivity.EXTRA_OPTION;
import static android.server.wm.app.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
-import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITIES;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -88,7 +89,8 @@
@Override
protected void onStart() {
super.onStart();
- registerReceiver(mReceiver, new IntentFilter(TEST_ACTIVITY_ACTION_FINISH_SELF));
+ registerReceiver(mReceiver, new IntentFilter(TEST_ACTIVITY_ACTION_FINISH_SELF),
+ Context.RECEIVER_EXPORTED);
}
@Override
@@ -108,18 +110,19 @@
@Override
public void handleCommand(String command, Bundle data) {
+ final Bundle options = data.getParcelable(EXTRA_OPTION);
switch (command) {
case COMMAND_START_ACTIVITY:
final Intent startIntent = data.getParcelable(EXTRA_INTENT);
try {
- startActivity(startIntent);
+ startActivity(startIntent, options);
} catch (Exception e) {
Log.w(getTag(), "Failed to startActivity: " + startIntent, e);
}
break;
case COMMAND_START_ACTIVITIES:
final Parcelable[] intents = data.getParcelableArray(EXTRA_INTENTS);
- startActivities(Arrays.copyOf(intents, intents.length, Intent[].class));
+ startActivities(Arrays.copyOf(intents, intents.length, Intent[].class), options);
break;
case COMMAND_NAVIGATE_UP_TO:
final Intent intent = data.getParcelable(EXTRA_INTENT);
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/AndroidManifest.xml b/tests/framework/base/windowmanager/backgroundactivity/AppA/AndroidManifest.xml
index d4de7b1..bc8038a 100755
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/AndroidManifest.xml
@@ -25,6 +25,8 @@
<receiver android:name=".StartBackgroundActivityReceiver"
android:exported="true"/>
<receiver android:name=".SendPendingIntentReceiver"
+ android:exported="true"/>
+ <service android:name=".BackgroundActivityTestService"
android:exported="true"/>
<receiver android:name=".SimpleAdminReceiver"
android:permission="android.permission.BIND_DEVICE_ADMIN"
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/BackgroundActivityTestService.java b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/BackgroundActivityTestService.java
new file mode 100644
index 0000000..46ef78b
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/BackgroundActivityTestService.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.backgroundactivity.appa;
+
+import static android.server.wm.backgroundactivity.appa.Components.APP_A_BACKGROUND_ACTIVITY;
+import static android.server.wm.backgroundactivity.appa.Components.APP_A_START_ACTIVITY_RECEIVER;
+
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class BackgroundActivityTestService extends Service {
+ private final IBackgroundActivityTestService mBinder = new MyBinder();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder.asBinder();
+ }
+
+ private class MyBinder extends IBackgroundActivityTestService.Stub {
+ @Override
+ public PendingIntent generatePendingIntent(boolean isBroadcast) {
+ if (isBroadcast) {
+ // Create a pendingIntent to launch send broadcast to appA and appA will start
+ // background activity.
+ Intent newIntent = new Intent();
+ newIntent.setComponent(APP_A_START_ACTIVITY_RECEIVER);
+ return PendingIntent.getBroadcast(BackgroundActivityTestService.this, 0, newIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ } else {
+ // Create a pendingIntent to launch appA's BackgroundActivity
+ Intent newIntent = new Intent();
+ newIntent.setComponent(APP_A_BACKGROUND_ACTIVITY);
+ newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return PendingIntent.getActivity(BackgroundActivityTestService.this, 0, newIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ }
+ }
+ }
+}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/Components.java b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/Components.java
index 4f1dfcd..06eef12 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/Components.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/Components.java
@@ -33,6 +33,8 @@
component(Components.class, "StartBackgroundActivityReceiver");
public static final ComponentName APP_A_SIMPLE_ADMIN_RECEIVER =
component(Components.class, "SimpleAdminReceiver");
+ public static final ComponentName APP_A_BACKGROUND_ACTIVITY_TEST_SERVICE =
+ component(Components.class, "BackgroundActivityTestService");
/** Extra key constants for {@link #APP_A_FOREGROUND_ACTIVITY}. */
public static class ForegroundActivity {
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/ForegroundActivity.java b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/ForegroundActivity.java
index 9f92875..b4e810a 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/ForegroundActivity.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/ForegroundActivity.java
@@ -93,7 +93,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_LAUNCH_BACKGROUND_ACTIVITIES);
filter.addAction(ACTION_FINISH_ACTIVITY);
- registerReceiver(mReceiver, filter);
+ registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED);
}
@Override
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppB/AndroidManifest.xml b/tests/framework/base/windowmanager/backgroundactivity/AppB/AndroidManifest.xml
index 4f1bed6..756827f 100755
--- a/tests/framework/base/windowmanager/backgroundactivity/AppB/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppB/AndroidManifest.xml
@@ -23,6 +23,9 @@
android:name=".StartPendingIntentReceiver"
android:exported="true"/>
<activity
+ android:name=".StartPendingIntentActivity"
+ android:exported="true"/>
+ <activity
android:name=".ForegroundActivity"
android:exported="true" />
</application>
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/Components.java b/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/Components.java
index 9f72fbd..8f6cf1a 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/Components.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/Components.java
@@ -23,9 +23,16 @@
public static final ComponentName APP_B_FOREGROUND_ACTIVITY =
component(Components.class, "ForegroundActivity");
+ public static final ComponentName APP_B_START_PENDING_INTENT_ACTIVITY =
+ component(Components.class, "StartPendingIntentActivity");
public static final ComponentName APP_B_START_PENDING_INTENT_RECEIVER =
component(Components.class, "StartPendingIntentReceiver");
+ /** Extra key constants for {@link #APP_B_START_PENDING_INTENT_ACTIVITY} */
+ public static class StartPendingIntentActivity {
+ public static final String ALLOW_BAL_EXTRA = "ALLOW_BAL_EXTRA";
+ }
+
/** Extra key constants for {@link #APP_B_START_PENDING_INTENT_RECEIVER} */
public static class StartPendingIntentReceiver {
public static final String PENDING_INTENT_EXTRA = "PENDING_INTENT_EXTRA";
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/StartPendingIntentActivity.java b/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/StartPendingIntentActivity.java
new file mode 100644
index 0000000..c1e3f6d
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/StartPendingIntentActivity.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.backgroundactivity.appb;
+
+import static android.server.wm.backgroundactivity.appb.Components.StartPendingIntentActivity.ALLOW_BAL_EXTRA;
+import static android.server.wm.backgroundactivity.appb.Components.StartPendingIntentReceiver.PENDING_INTENT_EXTRA;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Receive pending intent from AppA and launch it
+ */
+public class StartPendingIntentActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle b) {
+ super.onCreate(b);
+ Intent intent = getIntent();
+ final PendingIntent pendingIntent = intent.getParcelableExtra(PENDING_INTENT_EXTRA);
+ final boolean allowBal = intent.getBooleanExtra(ALLOW_BAL_EXTRA, false);
+
+ try {
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(allowBal);
+ Bundle bundle = options.toBundle();
+ pendingIntent.send(/* context */ null, /* code */0, /* intent */
+ null, /* onFinished */null, /* handler */
+ null, /* requiredPermission */ null, bundle);
+ } catch (PendingIntent.CanceledException e) {
+ throw new AssertionError(e);
+ }
+ }
+}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp b/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp
index 58843ed..2b54222 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp
+++ b/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp
@@ -19,7 +19,7 @@
java_test {
name: "cts-background-activity-common",
- srcs: ["src/**/*.java"],
+ srcs: ["src/**/*.java", "aidl/**/*.aidl"],
static_libs: [
"androidx.annotation_annotation",
diff --git a/tests/framework/base/windowmanager/backgroundactivity/common/aidl/android/server/wm/backgroundactivity/appa/IBackgroundActivityTestService.aidl b/tests/framework/base/windowmanager/backgroundactivity/common/aidl/android/server/wm/backgroundactivity/appa/IBackgroundActivityTestService.aidl
new file mode 100644
index 0000000..405a290
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/common/aidl/android/server/wm/backgroundactivity/appa/IBackgroundActivityTestService.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.backgroundactivity.appa;
+
+import android.app.PendingIntent;
+
+interface IBackgroundActivityTestService {
+ PendingIntent generatePendingIntent(boolean isBroadcast);
+}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java b/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java
index e45e821..8ebac55 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java
@@ -21,6 +21,7 @@
import static android.server.wm.UiDeviceUtils.pressHomeButton;
import static android.server.wm.WindowManagerState.STATE_INITIALIZING;
import static android.server.wm.backgroundactivity.appa.Components.APP_A_BACKGROUND_ACTIVITY;
+import static android.server.wm.backgroundactivity.appa.Components.APP_A_BACKGROUND_ACTIVITY_TEST_SERVICE;
import static android.server.wm.backgroundactivity.appa.Components.APP_A_FOREGROUND_ACTIVITY;
import static android.server.wm.backgroundactivity.appa.Components.APP_A_SECOND_BACKGROUND_ACTIVITY;
import static android.server.wm.backgroundactivity.appa.Components.APP_A_SEND_PENDING_INTENT_RECEIVER;
@@ -37,6 +38,9 @@
import static android.server.wm.backgroundactivity.appa.Components.SendPendingIntentReceiver.IS_BROADCAST_EXTRA;
import static android.server.wm.backgroundactivity.appa.Components.StartBackgroundActivityReceiver.START_ACTIVITY_DELAY_MS_EXTRA;
import static android.server.wm.backgroundactivity.appb.Components.APP_B_FOREGROUND_ACTIVITY;
+import static android.server.wm.backgroundactivity.appb.Components.APP_B_START_PENDING_INTENT_ACTIVITY;
+import static android.server.wm.backgroundactivity.appb.Components.StartPendingIntentActivity.ALLOW_BAL_EXTRA;
+import static android.server.wm.backgroundactivity.appb.Components.StartPendingIntentReceiver.PENDING_INTENT_EXTRA;
import static android.server.wm.backgroundactivity.common.CommonComponents.EVENT_NOTIFIER_EXTRA;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
@@ -50,15 +54,20 @@
import static org.junit.Assert.assertTrue;
import android.Manifest;
+import android.app.PendingIntent;
import android.app.UiAutomation;
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.UserInfo;
+import android.os.IBinder;
import android.os.ResultReceiver;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.SystemUserOnly;
+import android.server.wm.backgroundactivity.appa.IBackgroundActivityTestService;
import android.server.wm.backgroundactivity.common.CommonComponents.Event;
import android.server.wm.backgroundactivity.common.EventReceiver;
@@ -74,6 +83,8 @@
import org.junit.Test;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Stream;
@@ -113,6 +124,9 @@
*/
private static final int BROADCAST_DELIVERY_TIMEOUT_MS = 60000;
+ private IBackgroundActivityTestService mBackgroundActivityTestService;
+ private ServiceConnection mBalServiceConnection;
+
@Override
@Before
public void setUp() throws Exception {
@@ -145,6 +159,9 @@
stopTestPackage(TEST_PACKAGE_APP_B);
AppOpsUtils.reset(APP_A_PACKAGE_NAME);
AppOpsUtils.reset(SHELL_PACKAGE);
+ if (mBalServiceConnection != null) {
+ mContext.unbindService(mBalServiceConnection);
+ }
}
@Test
@@ -415,6 +432,21 @@
}
@Test
+ public void testPendingIntentActivity_whenActivityAllowsBal_isNotBlocked() throws Exception {
+ startPendingIntentSenderActivity(true);
+ boolean result = waitForActivityFocused(APP_A_BACKGROUND_ACTIVITY);
+ assertTrue("Not able to launch background activity", result);
+ assertTaskStack(new ComponentName[]{APP_A_BACKGROUND_ACTIVITY}, APP_A_BACKGROUND_ACTIVITY);
+ }
+
+ @Test
+ public void testPendingIntentActivity_whenActivityDoesNotAllowBal_isBlocked() throws Exception {
+ startPendingIntentSenderActivity(false);
+ boolean result = waitForActivityFocused(APP_A_BACKGROUND_ACTIVITY);
+ assertFalse("Should not able to launch background activity", result);
+ }
+
+ @Test
@FlakyTest(bugId = 130800326)
public void testPendingIntentActivityNotBlocked_appAIsForeground() throws Exception {
// Start AppA foreground activity
@@ -730,6 +762,49 @@
return waitForActivityFocused(ACTIVITY_FOCUS_TIMEOUT_MS, componentName);
}
+ private void setupPendingIntentService() throws Exception {
+ Intent bindIntent = new Intent();
+ bindIntent.setComponent(APP_A_BACKGROUND_ACTIVITY_TEST_SERVICE);
+ final CountDownLatch bindLatch = new CountDownLatch(1);
+
+ mBalServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mBackgroundActivityTestService =
+ IBackgroundActivityTestService.Stub.asInterface(service);
+ bindLatch.countDown();
+ }
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mBackgroundActivityTestService = null;
+ }
+ };
+ boolean success = mContext.bindService(bindIntent, mBalServiceConnection,
+ Context.BIND_AUTO_CREATE);
+ assertTrue(success);
+ assertTrue("Timeout connecting to test service",
+ bindLatch.await(1000, TimeUnit.MILLISECONDS));
+ }
+
+ private void startPendingIntentSenderActivity(boolean allowBal) throws Exception {
+ setupPendingIntentService();
+ // Get a PendingIntent created by appA.
+ final PendingIntent pi;
+ try {
+ pi = mBackgroundActivityTestService.generatePendingIntent(false);
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ }
+
+ // Start app B's activity so it runs send() on PendingIntent created by app A.
+ Intent secondIntent = new Intent();
+ secondIntent.setComponent(APP_B_START_PENDING_INTENT_ACTIVITY);
+ secondIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ secondIntent.putExtra(PENDING_INTENT_EXTRA, pi);
+ secondIntent.putExtra(ALLOW_BAL_EXTRA, allowBal);
+ mContext.startActivity(secondIntent);
+ }
+
private void sendPendingIntentActivity() {
Intent intent = new Intent();
intent.setComponent(APP_A_SEND_PENDING_INTENT_RECEIVER);
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ExitAnimationActivity.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ExitAnimationActivity.java
index ded84a7..30e99b0 100644
--- a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ExitAnimationActivity.java
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ExitAnimationActivity.java
@@ -47,7 +47,8 @@
protected void onStart() {
super.onStart();
registerReceiver(mReceiver,
- new IntentFilter(Components.ExitAnimationActivityReceiver.ACTION_FINISH));
+ new IntentFilter(Components.ExitAnimationActivityReceiver.ACTION_FINISH),
+ Context.RECEIVER_EXPORTED);
}
@Override
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
index 122ef2a..919eed8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
@@ -21,8 +21,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
import android.app.Activity;
import android.app.ActivityOptions;
@@ -53,30 +51,19 @@
*/
@Presubmit
public class ActivityTransitionTests extends ActivityManagerTestBase {
- // See WindowManagerService.DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
- static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
- "persist.wm.disable_custom_task_animation";
- static final boolean DISABLE_CUSTOM_TASK_ANIMATION_DEFAULT = true;
-
// Duration of the default wallpaper close animation
static final long DEFAULT_ANIMATION_DURATION = 275L;
// Duration of the R.anim.alpha animation
static final long CUSTOM_ANIMATION_DURATION = 2000L;
- private static boolean customTaskAnimationDisabled() {
- try {
- return Integer.parseInt(executeShellCommand(
- "getprop " + DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY).replace("\n", "")) != 0;
- } catch (NumberFormatException e) {
- return DISABLE_CUSTOM_TASK_ANIMATION_DEFAULT;
- }
- }
+ // Allowable error for the measured animation duration.
+ static final long EXPECTED_DURATION_TOLERANCE_START = 200;
+ static final long EXPECTED_DURATION_TOLERANCE_FINISH = 1000;
- @Test
+ @Test
public void testActivityTransitionDurationNoShortenAsExpected() throws Exception {
- final long expectedDurationMs = CUSTOM_ANIMATION_DURATION - 100L;
- final long minDurationMs = expectedDurationMs;
- final long maxDurationMs = expectedDurationMs + 300L;
+ final long minDurationMs = CUSTOM_ANIMATION_DURATION - EXPECTED_DURATION_TOLERANCE_START;
+ final long maxDurationMs = CUSTOM_ANIMATION_DURATION + EXPECTED_DURATION_TOLERANCE_FINISH;
final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
final CountDownLatch latch = new CountDownLatch(1);
@@ -114,51 +101,9 @@
}
@Test
- public void testTaskTransitionDurationNoShortenAsExpected() throws Exception {
- assumeFalse(customTaskAnimationDisabled());
-
- final long expectedDurationMs = CUSTOM_ANIMATION_DURATION - 100L;
- final long minDurationMs = expectedDurationMs;
- final long maxDurationMs = expectedDurationMs + 300L;
- final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
-
- final CountDownLatch latch = new CountDownLatch(1);
- AtomicLong transitionStartTime = new AtomicLong();
- AtomicLong transitionEndTime = new AtomicLong();
-
- final ActivityOptions.OnAnimationStartedListener startedListener = () -> {
- transitionStartTime.set(SystemClock.elapsedRealtime());
- };
-
- final ActivityOptions.OnAnimationFinishedListener finishedListener = () -> {
- transitionEndTime.set(SystemClock.elapsedRealtime());
- latch.countDown();
- };
-
- final Bundle bundle = ActivityOptions.makeCustomAnimation(mContext,
- R.anim.alpha, 0, new Handler(Looper.getMainLooper()), startedListener,
- finishedListener).toBundle();
- final Intent intent = new Intent().setComponent(TEST_ACTIVITY)
- .addFlags(FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(intent, bundle);
- mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
- waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
- "Activity must be launched");
-
- latch.await(2, TimeUnit.SECONDS);
- final long totalTime = transitionEndTime.get() - transitionStartTime.get();
- assertTrue("Actual transition duration should be in the range "
- + "<" + minDurationMs + ", " + maxDurationMs + "> ms, "
- + "actual=" + totalTime, durationRange.contains(totalTime));
- }
-
- @Test
public void testTaskTransitionOverrideDisabled() throws Exception {
- assumeTrue(customTaskAnimationDisabled());
-
- final long expectedDurationMs = DEFAULT_ANIMATION_DURATION - 100L;
- final long minDurationMs = expectedDurationMs;
- final long maxDurationMs = expectedDurationMs + 1000L;
+ final long minDurationMs = DEFAULT_ANIMATION_DURATION - EXPECTED_DURATION_TOLERANCE_START;
+ final long maxDurationMs = DEFAULT_ANIMATION_DURATION + EXPECTED_DURATION_TOLERANCE_FINISH;
final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
final CountDownLatch latch = new CountDownLatch(1);
@@ -195,11 +140,8 @@
@Test
public void testTaskTransitionOverride() throws Exception {
- assumeTrue(customTaskAnimationDisabled());
-
- final long expectedDurationMs = CUSTOM_ANIMATION_DURATION - 100L;
- final long minDurationMs = expectedDurationMs;
- final long maxDurationMs = expectedDurationMs + 1000L;
+ final long minDurationMs = CUSTOM_ANIMATION_DURATION - EXPECTED_DURATION_TOLERANCE_START;
+ final long maxDurationMs = CUSTOM_ANIMATION_DURATION + EXPECTED_DURATION_TOLERANCE_FINISH;
final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
final CountDownLatch latch = new CountDownLatch(1);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
index 401ebaf..74ef09e 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
@@ -24,7 +24,6 @@
import static android.server.wm.CliIntentExtra.extraString;
import static android.server.wm.UiDeviceUtils.pressBackButton;
import static android.server.wm.UiDeviceUtils.pressHomeButton;
-import static android.server.wm.UiDeviceUtils.pressSleepButton;
import static android.server.wm.VirtualDisplayHelper.waitForDefaultDisplayState;
import static android.server.wm.WindowManagerState.STATE_RESUMED;
import static android.server.wm.WindowManagerState.STATE_STOPPED;
@@ -327,9 +326,11 @@
}
getLaunchActivityBuilder().setTargetActivity(BROADCAST_RECEIVER_ACTIVITY)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
.setIntentFlags(FLAG_ACTIVITY_NEW_TASK).execute();
getLaunchActivityBuilder().setTargetActivity(BROADCAST_RECEIVER_ACTIVITY)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
.setIntentFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME).execute();
mBroadcastActionTrigger.finishBroadcastReceiverActivity();
@@ -349,30 +350,27 @@
}
// Start LaunchingActivity and BroadcastReceiverActivity in two separate tasks.
getLaunchActivityBuilder().setTargetActivity(BROADCAST_RECEIVER_ACTIVITY)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
.setIntentFlags(FLAG_ACTIVITY_NEW_TASK).execute();
waitAndAssertResumedActivity(BROADCAST_RECEIVER_ACTIVITY,"Activity must be resumed");
- final int taskId1 = mWmState.getTaskByActivity(LAUNCHING_ACTIVITY).mTaskId;
- final int taskId2 = mWmState.getTaskByActivity(BROADCAST_RECEIVER_ACTIVITY).mTaskId;
+ final int taskId = mWmState.getTaskByActivity(BROADCAST_RECEIVER_ACTIVITY).mTaskId;
try {
- runWithShellPermission(() -> {
- mAtm.startSystemLockTaskMode(taskId1);
- mAtm.startSystemLockTaskMode(taskId2);
- });
+ runWithShellPermission(() -> mAtm.startSystemLockTaskMode(taskId));
getLaunchActivityBuilder()
.setUseInstrumentation()
.setTargetActivity(BROADCAST_RECEIVER_ACTIVITY)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
.setIntentFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME).execute();
- waitAndAssertResumedActivity(BROADCAST_RECEIVER_ACTIVITY,"Activity must be resumed");
- mBroadcastActionTrigger.finishBroadcastReceiverActivity();
- mWmState.waitAndAssertActivityRemoved(BROADCAST_RECEIVER_ACTIVITY);
-
- mWmState.assertHomeActivityVisible(false);
+ mWmState.waitForActivityState(BROADCAST_RECEIVER_ACTIVITY, STATE_RESUMED);
} finally {
- runWithShellPermission(() -> {
- mAtm.stopSystemLockTaskMode();
- });
+ runWithShellPermission(() -> mAtm.stopSystemLockTaskMode());
}
+
+ mBroadcastActionTrigger.finishBroadcastReceiverActivity();
+ mWmState.waitAndAssertActivityRemoved(BROADCAST_RECEIVER_ACTIVITY);
+
+ mWmState.assertHomeActivityVisible(false);
}
@Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AnimationBackgroundTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AnimationBackgroundTests.java
new file mode 100644
index 0000000..d85a255
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AnimationBackgroundTests.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.wm.app.Components.BackgroundActivityTransition.TRANSITION_REQUESTED;
+import static android.server.wm.app.Components.CLEAR_BACKGROUND_TRANSITION_EXIT_ACTIVITY;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.compatibility.common.util.TestUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:AnimationBackgroundTests
+ */
+@Presubmit
+@android.server.wm.annotation.Group1
+public class AnimationBackgroundTests extends ActivityManagerTestBase {
+
+ @Rule
+ public final DumpOnFailure dumpOnFailure = new DumpOnFailure();
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mWmState.setSanityCheckWithFocusedWindow(false);
+ mWmState.waitForDisplayUnfrozen();
+ }
+
+ @After
+ public void tearDown() {
+ mWmState.setSanityCheckWithFocusedWindow(true);
+ }
+
+ @Test
+ public void testBackgroundColorShowsDuringActivityTransition() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS);
+
+ final List<WindowManagerState.WindowState> windows = getWmState().getWindows();
+ Optional<WindowManagerState.WindowState> screenDecorOverlay =
+ windows.stream().filter(
+ w -> w.getName().equals("ScreenDecorOverlay")).findFirst();
+ Optional<WindowManagerState.WindowState> screenDecorOverlayBottom =
+ windows.stream().filter(
+ w -> w.getName().equals("ScreenDecorOverlayBottom")).findFirst();
+ getWmState().getWindowStateForAppToken("screenDecorOverlay");
+ final int screenDecorOverlayHeight = screenDecorOverlay.map(
+ WindowManagerState.WindowState::getRequestedHeight).orElse(0);
+ final int screenDecorOverlayBottomHeight = screenDecorOverlayBottom.map(
+ WindowManagerState.WindowState::getRequestedHeight).orElse(0);
+
+ TestJournalProvider.TestJournalContainer.start();
+ final TestJournalProvider.TestJournal journal = TestJournalProvider.TestJournalContainer
+ .get(CLEAR_BACKGROUND_TRANSITION_EXIT_ACTIVITY);
+ launchActivityInNewTask(CLEAR_BACKGROUND_TRANSITION_EXIT_ACTIVITY);
+
+ try {
+ TestUtils.waitUntil("Waiting for app to complete work", 15 /* timeoutSecond */,
+ () -> journal.extras.getBoolean(TRANSITION_REQUESTED));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ // The activity transition is set to last 5 seconds, wait half a second to make sure
+ // the activity transition has started after we receive confirmation through the test
+ // journal that we have requested to start a new activity.
+ SystemClock.sleep(500);
+
+ // Take a screenshot during the transition where we hide both the activities to just
+ // show the background of the transition which is set to be white.
+ final Bitmap screenshot = takeScreenshot();
+ final float[] white = new float[] {1, 1, 1};
+ for (int x = 0; x < screenshot.getWidth(); x++) {
+ for (int y = screenDecorOverlayHeight;
+ y < screenshot.getHeight() - screenDecorOverlayBottomHeight; y++) {
+ final Color c = screenshot.getColor(x, y);
+ assertArrayEquals("Transition Background pixel (" + x + ", " + y + ") is not white",
+ white, new float[] {c.red(), c.green(), c.blue()}, 0);
+ }
+ }
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
index 07df361..d52f95c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
@@ -48,9 +48,9 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
@@ -58,7 +58,6 @@
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
@@ -69,7 +68,6 @@
import android.server.wm.CommandSession.ConfigInfo;
import android.server.wm.CommandSession.SizeInfo;
import android.server.wm.TestJournalProvider.TestJournalContainer;
-import android.util.DisplayMetrics;
import android.view.Display;
import org.junit.Test;
@@ -140,6 +138,8 @@
@Test
public void testConfigurationUpdatesWhenRotatingWhileFullscreen() {
assumeTrue("Skipping test: no rotation support", supportsRotation());
+ // TODO(b/209920544) remove assumeFalse after issue fix.
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
final RotationSession rotationSession = createManagedRotationSession();
rotationSession.set(ROTATION_0);
@@ -163,6 +163,8 @@
public void testConfigurationUpdatesWhenRotatingWhileDocked() {
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+ // TODO(b/209920544) remove assumeFalse after issue fix.
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
final ActivitySessionClient resizeableActivityClient = createManagedActivityClientSession();
final RotationSession rotationSession = createManagedRotationSession();
rotationSession.set(ROTATION_0);
@@ -189,6 +191,8 @@
public void testConfigurationUpdatesWhenRotatingToSideFromDocked() {
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+ // TODO(b/209920544) remove assumeFalse after issue fix.
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
final ActivitySessionClient resizeableActivityClient = createManagedActivityClientSession();
final RotationSession rotationSession = createManagedRotationSession();
rotationSession.set(ROTATION_0);
@@ -366,6 +370,9 @@
ORIENTATION_PORTRAIT, initialReportedSizes.orientation);
assertTrue("portrait activity should have height >= width",
initialReportedSizes.heightDp >= initialReportedSizes.widthDp);
+ assumeFalse("Skipping test: device is fixed to user rotation",
+ mWmState.isFixedToUserRotation());
+
separateTestJournal();
launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
@@ -494,11 +501,10 @@
assertEquals("The last reported size should be the same as the one from onCreate",
reportedSizes, onCreateConfigInfo.sizeInfo);
- final Display display = mDm.getDisplay(Display.DEFAULT_DISPLAY);
- final Point expectedRealDisplaySize = new Point();
- display.getRealSize(expectedRealDisplaySize);
-
- final int expectedRotation = display.getRotation();
+ final WindowManagerState.DisplayContent dc = mWmState.getDisplay(Display.DEFAULT_DISPLAY);
+ final Point expectedRealDisplaySize =
+ new Point(dc.getDisplayRect().width(), dc.getDisplayRect().height());
+ final int expectedRotation = mWmState.getRotation();
assertEquals("The activity should get the final display rotation in onCreate",
expectedRotation, onCreateConfigInfo.rotation);
assertEquals("The application should get the final display rotation in onCreate",
@@ -517,16 +523,16 @@
appConfigInfo.sizeInfo.displayWidth > appConfigInfo.sizeInfo.displayHeight);
assertEquals("The app display metrics must be landscape", isLandscape,
appConfigInfo.sizeInfo.metricsWidth > appConfigInfo.sizeInfo.metricsHeight);
-
- final DisplayMetrics globalMetrics = Resources.getSystem().getDisplayMetrics();
assertEquals("The display metrics of system resources must be landscape",
- new Point(globalMetrics.widthPixels, globalMetrics.heightPixels),
+ new Point(dc.getAppRect().width(), dc.getAppRect().height()),
new Point(globalSizeInfo.metricsWidth, globalSizeInfo.metricsHeight));
}
@Test
public void testTranslucentActivityPermitted() throws Exception {
assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest());
+ // TODO(b/209920544) remove assumeFalse after issue fix.
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
final RotationSession rotationSession = createManagedRotationSession();
rotationSession.set(ROTATION_0);
@@ -548,6 +554,8 @@
// Start landscape activity.
launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+ assumeFalse("Skipping test: device is fixed to user rotation",
+ mWmState.isFixedToUserRotation());
mWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
mWmState.waitAndAssertLastOrientation("Fullscreen app requested landscape orientation",
SCREEN_ORIENTATION_LANDSCAPE);
@@ -623,6 +631,8 @@
public void testAppOrientationWhenRotating() throws Exception {
assumeTrue("Skipping test: no rotation support", supportsRotation());
+ // TODO(b/209920544) remove assumeFalse after issue fix.
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
// Start resizeable activity that handles configuration changes.
separateTestJournal();
launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
@@ -694,6 +704,8 @@
*/
private void assertDisplayContextDoesntChangeOrientationWhenRotating(
Function<Activity, Context> baseContextSupplier) {
+ // TODO(b/209920544) remove assumeFalse after issue fix.
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
RotationSession rotationSession = createManagedRotationSession();
rotationSession.set(ROTATION_0);
@@ -791,8 +803,19 @@
// Start landscape activity.
launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
mWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
- mWmState.waitAndAssertLastOrientation("Fullscreen app requested landscape orientation",
- SCREEN_ORIENTATION_LANDSCAPE);
+ final boolean isFixedToUserRotation = mWmState.isFixedToUserRotation();
+ if (!isFixedToUserRotation) {
+ mWmState.waitAndAssertLastOrientation(
+ "Fullscreen app requested landscape orientation",
+ SCREEN_ORIENTATION_LANDSCAPE);
+ } else {
+ final SizeInfo reportedSizes =
+ getLastReportedSizesForActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
+ assertEquals("landscape activity should be in landscape",
+ ORIENTATION_LANDSCAPE, reportedSizes.orientation);
+ assertTrue("landscape activity should have height < width",
+ reportedSizes.heightDp < reportedSizes.widthDp);
+ }
// Start another activity in a different task.
launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
@@ -807,8 +830,18 @@
// Verify that activity brought to front is in originally requested orientation.
mWmState.waitForValidState(LANDSCAPE_ORIENTATION_ACTIVITY);
- mWmState.waitAndAssertLastOrientation("Should return to app in landscape orientation",
- SCREEN_ORIENTATION_LANDSCAPE);
+ if (!isFixedToUserRotation) {
+ mWmState.waitAndAssertLastOrientation(
+ "Fullscreen app requested landscape orientation",
+ SCREEN_ORIENTATION_LANDSCAPE);
+ } else {
+ final SizeInfo reportedSizes =
+ getLastReportedSizesForActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
+ assertEquals("landscape activity should be in landscape",
+ ORIENTATION_LANDSCAPE, reportedSizes.orientation);
+ assertTrue("landscape activity should have height < width",
+ reportedSizes.heightDp < reportedSizes.widthDp);
+ }
}
/**
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AspectRatioTestsBase.java b/tests/framework/base/windowmanager/src/android/server/wm/AspectRatioTestsBase.java
index 96cf5c2..efaed22 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AspectRatioTestsBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AspectRatioTestsBase.java
@@ -30,7 +30,7 @@
import androidx.test.rule.ActivityTestRule;
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
import org.hamcrest.Matcher;
import org.junit.Before;
@@ -66,7 +66,7 @@
void runAspectRatioTest(final ActivityTestRule activityRule,
final AssertAspectRatioCallback callback) {
final Activity activity = launchActivity(activityRule);
- PollingCheck.waitFor(activity::hasWindowFocus);
+ WindowUtil.waitForFocus(activity);
try {
final Point displaySize = new Point();
getDisplay(activity).getSize(displaySize);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/BlurTests.java b/tests/framework/base/windowmanager/src/android/server/wm/BlurTests.java
index 23e47c2..297eaf0 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/BlurTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/BlurTests.java
@@ -363,10 +363,11 @@
}
private static void assertBackgroundBlurOverBlurBehind(Bitmap screenshot, Rect windowFrame) {
- // We are assuming that the background blur will become bigger by roughly half of the blur
- // behind radius
- assertBlur(screenshot, BACKGROUND_BLUR_PX + ((int) (BLUR_BEHIND_PX*0.5f)),
- windowFrame.top, windowFrame.bottom);
+ assertBlur(
+ screenshot,
+ (int) Math.sqrt(Math.pow(BACKGROUND_BLUR_PX, 2.f) + Math.pow(BLUR_BEHIND_PX, 2.f)),
+ windowFrame.top,
+ windowFrame.bottom);
}
private static void assertNoBlurBehind(Bitmap screenshot, Rect windowFrame) {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java b/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java
index 0c56169..d53a168 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java
@@ -87,6 +87,8 @@
private static final ComponentName SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY =
component(SupportsSizeChangesPortraitActivity.class);
+ // Fixed orientation min aspect ratio
+ private static final float FIXED_ORIENTATION_MIN_ASPECT_RATIO = 1.03f;
// The min aspect ratio of NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY (as defined in the manifest).
private static final float ACTIVITY_MIN_ASPECT_RATIO = 1.6f;
// The min aspect ratio of NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY (as defined in the
@@ -108,7 +110,8 @@
mDisplayMetricsSession =
createManagedDisplayMetricsSession(DEFAULT_DISPLAY);
- createManagedIgnoreOrientationRequestSession(DEFAULT_DISPLAY, /* value= */ true);
+ createManagedLetterboxAspectRatioSession(DEFAULT_DISPLAY,
+ FIXED_ORIENTATION_MIN_ASPECT_RATIO);
createManagedConstrainDisplayApisFlagsSession();
}
@@ -487,9 +490,12 @@
*/
private void runSizeCompatTest(ComponentName activity, int windowingMode,
boolean inSizeCompatModeAfterResize) {
+ mWmState.computeState();
+ WindowManagerState.DisplayContent originalDC = mWmState.getDisplay(DEFAULT_DISPLAY);
+
runSizeCompatTest(activity, windowingMode, /* resizeRatio= */ 0.5,
inSizeCompatModeAfterResize);
- restoreDisplay(activity);
+ waitForRestoreDisplay(originalDC);
runSizeCompatTest(activity, windowingMode, /* resizeRatio= */ 2,
inSizeCompatModeAfterResize);
}
@@ -552,11 +558,15 @@
private void runSizeCompatModeSandboxTest(ComponentName activity, boolean isSandboxed,
boolean inSizeCompatModeAfterResize) {
assertThat(getInitialDisplayAspectRatio()).isLessThan(ACTIVITY_LARGE_MIN_ASPECT_RATIO);
+
+ mWmState.computeState();
+ WindowManagerState.DisplayContent originalDC = mWmState.getDisplay(DEFAULT_DISPLAY);
+
runSizeCompatTest(activity, WINDOWING_MODE_FULLSCREEN, /* resizeRatio= */ 0.5,
inSizeCompatModeAfterResize);
assertSandboxedByProvidesMaxBounds(activity, isSandboxed);
- restoreDisplay(activity);
- runSizeCompatTest(activity, WINDOWING_MODE_FULLSCREEN, /* resizeRatio= */ 2,
+ waitForRestoreDisplay(originalDC);
+ runSizeCompatTest(activity, WINDOWING_MODE_FULLSCREEN, /* resizeRatio=*/ 2,
inSizeCompatModeAfterResize);
assertSandboxedByProvidesMaxBounds(activity, isSandboxed);
}
@@ -706,6 +716,18 @@
}
/**
+ * Wait for the display to be restored to the original display content.
+ */
+ private void waitForRestoreDisplay(WindowManagerState.DisplayContent originalDisplayContent) {
+ mWmState.waitForWithAmState(wmState -> {
+ mDisplayMetricsSession.restoreDisplayMetrics();
+ WindowManagerState.DisplayContent dc = mWmState.getDisplay(DEFAULT_DISPLAY);
+ return dc.equals(originalDisplayContent);
+ }, "waiting for display to be restored");
+ }
+
+
+ /**
* Resize the display and ensure configuration changes are complete.
*/
private void resizeDisplay(ComponentName activity, double sizeRatio) {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
index 28608d0..94731c0 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
@@ -21,6 +21,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.provider.Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS;
import static android.server.wm.DisplayCutoutTests.TestActivity.EXTRA_CUTOUT_MODE;
import static android.server.wm.DisplayCutoutTests.TestActivity.EXTRA_ORIENTATION;
import static android.server.wm.DisplayCutoutTests.TestDef.Which.DISPATCHED;
@@ -58,6 +59,8 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.server.wm.settings.SettingsSession;
import android.view.DisplayCutout;
import android.view.View;
import android.view.ViewGroup;
@@ -68,11 +71,14 @@
import androidx.test.rule.ActivityTestRule;
import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
import org.hamcrest.CustomTypeSafeMatcher;
import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher;
+import org.junit.AfterClass;
import org.junit.Assert;
+import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
@@ -94,6 +100,8 @@
@android.server.wm.annotation.Group3
@RunWith(Parameterized.class)
public class DisplayCutoutTests {
+ private static SettingsSession<String> sImmersiveModeConfirmationSetting;
+
static final String LEFT = "left";
static final String TOP = "top";
static final String RIGHT = "right";
@@ -123,6 +131,21 @@
new ActivityTestRule<>(TestActivity.class, false /* initialTouchMode */,
false /* launchActivity */);
+ @BeforeClass
+ public static void setUpClass() {
+ sImmersiveModeConfirmationSetting = new SettingsSession<>(
+ Settings.Secure.getUriFor(IMMERSIVE_MODE_CONFIRMATIONS),
+ Settings.Secure::getString, Settings.Secure::putString);
+ sImmersiveModeConfirmationSetting.set("confirmed");
+ }
+
+ @AfterClass
+ public static void tearDownClass() {
+ if (sImmersiveModeConfirmationSetting != null) {
+ sImmersiveModeConfirmationSetting.close();
+ }
+ }
+
@Test
public void testConstructor() {
final Insets safeInsets = Insets.of(1, 2, 3, 4);
@@ -174,6 +197,29 @@
}
@Test
+ public void testBuilder() {
+ final Insets safeInsets = Insets.of(1, 2, 1, 0);
+ final Insets waterfallInsets = Insets.of(1, 0, 1, 0);
+ final Rect boundingRectTop = new Rect(10, 0, 20, 2);
+ final Path cutoutPath = new Path();
+
+ final DisplayCutout displayCutout = new DisplayCutout.Builder()
+ .setSafeInsets(safeInsets)
+ .setWaterfallInsets(waterfallInsets)
+ .setBoundingRectTop(boundingRectTop)
+ .setCutoutPath(cutoutPath)
+ .build();
+
+ assertEquals(safeInsets.left, displayCutout.getSafeInsetLeft());
+ assertEquals(safeInsets.top, displayCutout.getSafeInsetTop());
+ assertEquals(safeInsets.right, displayCutout.getSafeInsetRight());
+ assertEquals(safeInsets.bottom, displayCutout.getSafeInsetBottom());
+ assertEquals(waterfallInsets, displayCutout.getWaterfallInsets());
+ assertEquals(boundingRectTop, displayCutout.getBoundingRectTop());
+ assertEquals(cutoutPath, displayCutout.getCutoutPath());
+ }
+
+ @Test
public void testDisplayCutout_default() {
runTest(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
(activity, insets, displayCutout, which) -> {
@@ -526,7 +572,7 @@
final T activity = rule.launchActivity(
new Intent().putExtra(EXTRA_CUTOUT_MODE, cutoutMode)
.putExtra(EXTRA_ORIENTATION, orientation));
- PollingCheck.waitFor(activity::hasWindowFocus);
+ WindowUtil.waitForFocus(activity);
PollingCheck.waitFor(() -> {
final Rect appBounds = getAppBounds(activity);
final Point displaySize = new Point();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
index 366f4dd..1605508 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
@@ -16,6 +16,9 @@
package android.server.wm;
+import static android.server.wm.UiDeviceUtils.pressUnlockButton;
+import static android.server.wm.UiDeviceUtils.pressWakeupButton;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM;
@@ -31,14 +34,17 @@
import android.app.Activity;
import android.app.Instrumentation;
+import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.PowerManager;
import android.platform.test.annotations.Presubmit;
import android.view.Gravity;
+import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
@@ -61,6 +67,7 @@
import org.junit.Test;
import java.util.ArrayList;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
@@ -68,12 +75,9 @@
@Presubmit
public class DisplayHashManagerTest {
- //TODO (b/195136026): There's currently know way to know when the buffer has been drawn in
- // SurfaceFlinger. Use sleep for now to make sure it's been drawn. Once b/195136026 is
- // completed, port this code to listen for the transaction complete so we can be sure the buffer
- // has been latched.
- private static final int SLEEP_TIME_MS = 1000;
+ private static final int WAIT_TIME_S = 5;
+ private final Point mCenter = new Point();
private final Point mTestViewSize = new Point(200, 300);
private Instrumentation mInstrumentation;
@@ -89,6 +93,8 @@
private SyncDisplayHashResultCallback mSyncDisplayHashResultCallback;
+ protected WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
+
@Rule
public ActivityTestRule<TestActivity> mActivityRule =
new ActivityTestRule<>(TestActivity.class);
@@ -96,16 +102,27 @@
@Before
public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
- Context context = mInstrumentation.getContext();
+ final Context context = mInstrumentation.getContext();
+ final KeyguardManager km = context.getSystemService(KeyguardManager.class);
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClass(context, TestActivity.class);
mActivity = mActivityRule.getActivity();
+ if (km != null && km.isKeyguardLocked() || !Objects.requireNonNull(
+ context.getSystemService(PowerManager.class)).isInteractive()) {
+ pressWakeupButton();
+ pressUnlockButton();
+ }
+
mActivity.runOnUiThread(() -> {
mMainView = new RelativeLayout(mActivity);
mActivity.setContentView(mMainView);
});
mInstrumentation.waitForIdleSync();
+ mActivity.runOnUiThread(() -> {
+ mCenter.set((mMainView.getWidth() - mTestViewSize.x) / 2,
+ (mMainView.getHeight() - mTestViewSize.y) / 2);
+ });
mDisplayHashManager = context.getSystemService(DisplayHashManager.class);
Set<String> algorithms = mDisplayHashManager.getSupportedHashAlgorithms();
@@ -203,7 +220,10 @@
@Test
public void testGenerateDisplayHash_ViewOffscreen() {
- final CountDownLatch viewLayoutLatch = new CountDownLatch(2);
+ final CountDownLatch committedCallbackLatch = new CountDownLatch(1);
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.addTransactionCommittedListener(mExecutor, committedCallbackLatch::countDown);
+
mInstrumentation.runOnMainSync(() -> {
final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
mTestViewSize.y);
@@ -211,17 +231,12 @@
mTestView.setBackgroundColor(Color.BLUE);
mTestView.setX(-mTestViewSize.x);
- ViewTreeObserver viewTreeObserver = mTestView.getViewTreeObserver();
- viewTreeObserver.addOnGlobalLayoutListener(viewLayoutLatch::countDown);
- viewTreeObserver.registerFrameCommitCallback(viewLayoutLatch::countDown);
-
mMainView.addView(mTestView, p);
- mMainView.invalidate();
+ mMainView.getRootSurfaceControl().applyTransactionOnDraw(t);
});
mInstrumentation.waitForIdleSync();
try {
- viewLayoutLatch.await(5, TimeUnit.SECONDS);
- Thread.sleep(SLEEP_TIME_MS);
+ committedCallbackLatch.await(WAIT_TIME_S, TimeUnit.SECONDS);
} catch (InterruptedException e) {
}
@@ -237,7 +252,9 @@
final WindowManager wm = mActivity.getWindowManager();
final WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
- final CountDownLatch viewLayoutLatch = new CountDownLatch(2);
+ final CountDownLatch committedCallbackLatch = new CountDownLatch(1);
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.addTransactionCommittedListener(mExecutor, committedCallbackLatch::countDown);
mInstrumentation.runOnMainSync(() -> {
mMainView = new RelativeLayout(mActivity);
windowParams.width = mTestViewSize.x;
@@ -246,21 +263,27 @@
windowParams.flags = FLAG_LAYOUT_NO_LIMITS;
mActivity.addWindow(mMainView, windowParams);
- final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
- mTestViewSize.y);
- mTestView = new View(mActivity);
- mTestView.setBackgroundColor(Color.BLUE);
+ mMainView.getViewTreeObserver().addOnWindowAttachListener(
+ new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(
+ mTestViewSize.x,
+ mTestViewSize.y);
+ mTestView = new View(mActivity);
+ mTestView.setBackgroundColor(Color.BLUE);
+ mMainView.addView(mTestView, p);
+ mMainView.getRootSurfaceControl().applyTransactionOnDraw(t);
+ }
- ViewTreeObserver viewTreeObserver = mTestView.getViewTreeObserver();
- viewTreeObserver.addOnGlobalLayoutListener(viewLayoutLatch::countDown);
- viewTreeObserver.registerFrameCommitCallback(viewLayoutLatch::countDown);
-
- mMainView.addView(mTestView, p);
+ @Override
+ public void onWindowDetached() {
+ }
+ });
});
mInstrumentation.waitForIdleSync();
try {
- viewLayoutLatch.await(5, TimeUnit.SECONDS);
- Thread.sleep(SLEEP_TIME_MS);
+ committedCallbackLatch.await(WAIT_TIME_S, TimeUnit.SECONDS);
} catch (InterruptedException e) {
}
@@ -357,7 +380,9 @@
@Test
public void testGenerateAndVerifyDisplayHash_MultiColor() {
- final CountDownLatch viewLayoutLatch = new CountDownLatch(2);
+ final CountDownLatch committedCallbackLatch = new CountDownLatch(1);
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.addTransactionCommittedListener(mExecutor, committedCallbackLatch::countDown);
mInstrumentation.runOnMainSync(() -> {
final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
mTestViewSize.y);
@@ -376,17 +401,15 @@
linearLayout.addView(redView, redParams);
mTestView = linearLayout;
- ViewTreeObserver viewTreeObserver = mTestView.getViewTreeObserver();
- viewTreeObserver.addOnGlobalLayoutListener(viewLayoutLatch::countDown);
- viewTreeObserver.registerFrameCommitCallback(viewLayoutLatch::countDown);
-
+ mTestView.setX(mCenter.x);
+ mTestView.setY(mCenter.y);
mMainView.addView(mTestView, p);
- mMainView.invalidate();
+ mMainView.getRootSurfaceControl().applyTransactionOnDraw(t);
});
mInstrumentation.waitForIdleSync();
+ mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
try {
- viewLayoutLatch.await(5, TimeUnit.SECONDS);
- Thread.sleep(SLEEP_TIME_MS);
+ committedCallbackLatch.await(WAIT_TIME_S, TimeUnit.SECONDS);
} catch (InterruptedException e) {
}
@@ -412,22 +435,23 @@
}
private void setupChildView() {
- final CountDownLatch viewLayoutLatch = new CountDownLatch(2);
+ final CountDownLatch committedCallbackLatch = new CountDownLatch(1);
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.addTransactionCommittedListener(mExecutor, committedCallbackLatch::countDown);
+
mInstrumentation.runOnMainSync(() -> {
final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
mTestViewSize.y);
mTestView = new View(mActivity);
+ mTestView.setX(mCenter.x);
+ mTestView.setY(mCenter.y);
mTestView.setBackgroundColor(Color.BLUE);
- ViewTreeObserver viewTreeObserver = mTestView.getViewTreeObserver();
- viewTreeObserver.addOnGlobalLayoutListener(viewLayoutLatch::countDown);
- viewTreeObserver.registerFrameCommitCallback(viewLayoutLatch::countDown);
mMainView.addView(mTestView, p);
- mMainView.invalidate();
+ mMainView.getRootSurfaceControl().applyTransactionOnDraw(t);
});
mInstrumentation.waitForIdleSync();
try {
- viewLayoutLatch.await(5, TimeUnit.SECONDS);
- Thread.sleep(SLEEP_TIME_MS);
+ committedCallbackLatch.await(WAIT_TIME_S, TimeUnit.SECONDS);
} catch (InterruptedException e) {
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java b/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java
index 4a49d9a..aa30d0c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java
@@ -65,7 +65,7 @@
public void setUp() throws Exception {
super.setUp();
mPongReceiver = new PongReceiver();
- mContext.registerReceiver(mPongReceiver, new IntentFilter(PONG));
+ mContext.registerReceiver(mPongReceiver, new IntentFilter(PONG), Context.RECEIVER_EXPORTED);
}
@After
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardInputTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardInputTests.java
index 6ee0eb4..2dbc649 100755
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardInputTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardInputTests.java
@@ -30,7 +30,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.rule.ActivityTestRule;
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.Before;
import org.junit.Rule;
@@ -54,7 +54,7 @@
assumeTrue(supportsInsecureLock());
mActivity = mActivityRule.getActivity();
- PollingCheck.waitFor(mActivity::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity);
}
/**
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
index dc7c308..30c0555 100755
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
@@ -46,6 +46,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import android.content.ComponentName;
@@ -187,6 +188,8 @@
*/
@Test
public void testTranslucentShowWhenLockedActivity() {
+ // TODO(b/209906849) remove assumeFalse after issue fix.
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
final LockScreenSession lockScreenSession = createManagedLockScreenSession();
launchActivity(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
mWmState.computeState(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
@@ -219,6 +222,8 @@
@Test
public void testDialogShowWhenLockedActivity() {
+ // TODO(b/209906849) remove assumeFalse after issue fix.
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
final LockScreenSession lockScreenSession = createManagedLockScreenSession();
launchActivity(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY);
mWmState.computeState(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/LocationInWindowTests.java b/tests/framework/base/windowmanager/src/android/server/wm/LocationInWindowTests.java
index a4b0e52..1b21043 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/LocationInWindowTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/LocationInWindowTests.java
@@ -45,6 +45,7 @@
import androidx.test.rule.ActivityTestRule;
import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
import org.hamcrest.Matcher;
import org.junit.Before;
@@ -162,7 +163,7 @@
LayoutParams lp) {
final T activity = rule.launchActivity(
new Intent().putExtra(EXTRA_LAYOUT_PARAMS, lp));
- PollingCheck.waitFor(activity::hasWindowFocus);
+ WindowUtil.waitForFocus(activity);
return activity;
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/LocationOnScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/LocationOnScreenTests.java
index 9c9491c..b7b8376 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/LocationOnScreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/LocationOnScreenTests.java
@@ -54,6 +54,7 @@
import com.android.compatibility.common.util.BitmapUtils;
import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
import org.hamcrest.Matcher;
import org.junit.Assert;
@@ -148,7 +149,7 @@
LayoutParams lp) {
final T activity = rule.launchActivity(
new Intent().putExtra(EXTRA_LAYOUT_PARAMS, lp));
- PollingCheck.waitFor(activity::hasWindowFocus);
+ WindowUtil.waitForFocus(activity);
return activity;
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
index 3e5b5d6..b372d53 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
@@ -36,13 +36,13 @@
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.server.wm.WindowManagerState.WindowState;
+import android.util.DisplayMetrics;
import android.view.DisplayCutout;
import android.view.WindowMetrics;
import org.junit.Test;
import java.util.List;
-import android.util.DisplayMetrics;
/**
* Build/Install/Run:
@@ -121,12 +121,11 @@
// Use default density because ActivityInfo.WindowLayout is initialized by that.
final int minWidth = dpToPx(MIN_WIDTH_DP, DisplayMetrics.DENSITY_DEVICE_STABLE);
final int minHeight = dpToPx(MIN_HEIGHT_DP, DisplayMetrics.DENSITY_DEVICE_STABLE);
- final Rect containingRect = mWindowState.getContainingFrame();
+ final Rect parentFrame = mWindowState.getParentFrame();
final int cutoutSize = getCutoutSizeByHorGravity(GRAVITY_HOR_LEFT);
- assertEquals("Min width is incorrect", minWidth,
- containingRect.width() + cutoutSize);
- assertEquals("Min height is incorrect", minHeight, containingRect.height());
+ assertEquals("Min width is incorrect", minWidth, parentFrame.width() + cutoutSize);
+ assertEquals("Min height is incorrect", minHeight, parentFrame.height());
}
private void testLayout(
@@ -147,7 +146,7 @@
getDisplayAndWindowState(activityName, true);
- final Rect containingRect = mWindowState.getContainingFrame();
+ final Rect parentFrame = mWindowState.getParentFrame();
final WindowMetrics windowMetrics = mWm.getMaximumWindowMetrics();
final Rect stableBounds = new Rect(windowMetrics.getBounds());
stableBounds.inset(windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(
@@ -166,7 +165,7 @@
}
verifyFrameSizeAndPosition(vGravity, hGravity, expectedWidthPx, expectedHeightPx,
- containingRect, stableBounds);
+ parentFrame, stableBounds);
}
private void getDisplayAndWindowState(ComponentName activityName, boolean checkFocus)
@@ -196,24 +195,24 @@
private void verifyFrameSizeAndPosition(
int vGravity, int hGravity, int expectedWidthPx, int expectedHeightPx,
- Rect containingFrame, Rect parentFrame) {
+ Rect parentFrame, Rect stableBounds) {
final int cutoutSize = getCutoutSizeByHorGravity(hGravity);
assertEquals("Width is incorrect",
- expectedWidthPx, containingFrame.width() + cutoutSize);
- assertEquals("Height is incorrect", expectedHeightPx, containingFrame.height());
+ expectedWidthPx, parentFrame.width() + cutoutSize);
+ assertEquals("Height is incorrect", expectedHeightPx, parentFrame.height());
if (vGravity == GRAVITY_VER_TOP) {
- assertEquals("Should be on the top", parentFrame.top, containingFrame.top);
+ assertEquals("Should be on the top", stableBounds.top, parentFrame.top);
} else if (vGravity == GRAVITY_VER_BOTTOM) {
- assertEquals("Should be on the bottom", parentFrame.bottom, containingFrame.bottom);
+ assertEquals("Should be on the bottom", stableBounds.bottom, parentFrame.bottom);
}
if (hGravity == GRAVITY_HOR_LEFT) {
assertEquals("Should be on the left",
- parentFrame.left, containingFrame.left - cutoutSize);
+ stableBounds.left, parentFrame.left - cutoutSize);
} else if (hGravity == GRAVITY_HOR_RIGHT){
assertEquals("Should be on the right",
- parentFrame.right, containingFrame.right + cutoutSize);
+ stableBounds.right, parentFrame.right + cutoutSize);
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java
index d468092..a061f8c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java
@@ -26,10 +26,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
import android.content.ComponentName;
-import android.content.pm.PackageManager;
import android.platform.test.annotations.Presubmit;
import org.junit.Test;
@@ -68,21 +66,11 @@
private void assertDisplayRequestedMinimalPostProcessing(ComponentName name, boolean on) {
final int displayId = getDisplayId(name);
+
+ // TODO(b/202378408) verify that minimal post-processing is requested only if
+ // it's supported once we have a separate API for disabling on-device processing.
boolean requested = isMinimalPostProcessingRequested(displayId);
-
- PackageManager packageManager = mContext.getPackageManager();
- // For TV Android S is requesting minimal post processing regardless if it's supported,
- // because the same signal is used by HAL implementations to disable on-device processing.
- final boolean isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
- if (isTv) {
- // TODO(b/202378408): Verify that minimal post-processing is requested only if
- // it's supported once we have a separate API for disabling on-device processing.
- assertEquals(requested, on);
- return;
- }
-
- boolean supported = isMinimalPostProcessingSupported(displayId);
- assertTrue(supported ? requested == on : !requested);
+ assertEquals(requested, on);
}
@Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
index 58df952..e7d6650 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
@@ -570,7 +570,9 @@
waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
"Activity launched on secondary display must be resumed");
- tapOnDisplayCenter(DEFAULT_DISPLAY);
+ // Tap on task center to switch focus between displays. Using task center instead of
+ // display center to cover the multi window scenario.
+ tapOnTaskCenter(mWmState.getTaskByActivity(VIRTUAL_DISPLAY_ACTIVITY));
waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, DEFAULT_DISPLAY,
"Top activity must be on the primary display");
@@ -624,7 +626,9 @@
assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY),
pair(newDisplay.mId, TEST_ACTIVITY));
- tapOnDisplayCenter(DEFAULT_DISPLAY);
+ // Tap on task center to switch focus between displays. Using task center instead of
+ // display center to cover the multi window scenario.
+ tapOnTaskCenter(mWmState.getTaskByActivity(RESIZEABLE_ACTIVITY));
// Check that the activity on the primary display is the topmost resumed
waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
@@ -675,7 +679,9 @@
mWmState.assertFocusedAppOnDisplay("Activity on second display must be focused.",
VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId);
- tapOnDisplayCenter(DEFAULT_DISPLAY);
+ // Tap on task center to switch focus between displays. Using task center instead of
+ // display center to cover the multi window scenario.
+ tapOnTaskCenter(mWmState.getTaskByActivity(TEST_ACTIVITY));
waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
"Activity should be top resumed when tapped.");
@@ -869,7 +875,9 @@
waitAndAssertTopResumedActivity(SDK_27_TEST_ACTIVITY, newDisplay.mId,
"Activity launched on secondary display must be resumed and focused");
- tapOnDisplayCenter(DEFAULT_DISPLAY);
+ // Tap on task center to switch focus between displays. Using task center instead of
+ // display center to cover the multi window scenario.
+ tapOnTaskCenter(mWmState.getTaskByActivity(SDK_27_LAUNCHING_ACTIVITY));
waitAndAssertTopResumedActivity(SDK_27_LAUNCHING_ACTIVITY, DEFAULT_DISPLAY,
"Activity launched on default display must be resumed and focused");
assertEquals("There must be only one resumed activity in the package.", 1,
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
index cb2fb3f..2a22f37 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
@@ -17,6 +17,8 @@
package android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.server.wm.BarTestUtils.assumeHasBars;
+import static android.server.wm.MockImeHelper.createManagedMockImeSession;
import static android.server.wm.UiDeviceUtils.pressSleepButton;
import static android.server.wm.UiDeviceUtils.pressUnlockButton;
import static android.server.wm.UiDeviceUtils.pressWakeupButton;
@@ -28,12 +30,11 @@
import static android.server.wm.app.Components.TEST_LIVE_WALLPAPER_SERVICE;
import static android.server.wm.app.Components.TestLiveWallpaperKeys.COMPONENT;
import static android.server.wm.app.Components.TestLiveWallpaperKeys.ENGINE_DISPLAY_ID;
-import static android.server.wm.BarTestUtils.assumeHasBars;
-import static android.server.wm.MockImeHelper.createManagedMockImeSession;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
@@ -64,11 +65,12 @@
import android.os.Bundle;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
-import android.server.wm.WindowManagerState.DisplayContent;
import android.server.wm.TestJournalProvider.TestJournalContainer;
+import android.server.wm.WindowManagerState.DisplayContent;
import android.server.wm.WindowManagerState.WindowState;
import android.server.wm.intent.Activities;
import android.text.TextUtils;
+import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -76,7 +78,6 @@
import android.widget.EditText;
import android.widget.LinearLayout;
-import com.android.compatibility.common.util.ImeAwareEditText;
import com.android.compatibility.common.util.SystemUtil;
import com.android.compatibility.common.util.TestUtils;
import com.android.cts.mockime.ImeCommand;
@@ -497,9 +498,11 @@
final DisplayContent defDisplay = mWmState.getDisplay(DEFAULT_DISPLAY);
final ImeEventStream stream = mockImeSession.openEventStream();
- // Tap default display as top focused display & request focus on EditText to show
- // soft input.
- tapOnDisplayCenter(defDisplay.mId);
+ // Tap on the imeTestActivity task center instead of the display center because
+ // the activity might not be spanning the entire display
+ WindowManagerState.Task imeTestActivityTask = mWmState
+ .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName());
+ tapOnTaskCenter(imeTestActivityTask);
expectEvent(stream, editorMatcher("onStartInput",
imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
showSoftInputAndAssertImeShownOnDisplay(defDisplay.mId, imeTestActivitySession, stream);
@@ -511,8 +514,11 @@
imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
showSoftInputAndAssertImeShownOnDisplay(newDisplay.mId, imeTestActivitySession2, stream);
- // Tap default display again to make sure the IME window will come back.
- tapOnDisplayCenter(defDisplay.mId);
+ // Tap on the imeTestActivity task center instead of the display center because
+ // the activity might not be spanning the entire display
+ imeTestActivityTask = mWmState
+ .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName());
+ tapOnTaskCenter(imeTestActivityTask);
expectEvent(stream, editorMatcher("onStartInput",
imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
showSoftInputAndAssertImeShownOnDisplay(defDisplay.mId, imeTestActivitySession, stream);
@@ -698,13 +704,19 @@
.setDisplayId(DEFAULT_DISPLAY).execute();
waitAndAssertTopResumedActivity(imeTestActivitySession.getActivity().getComponentName(),
DEFAULT_DISPLAY, "Activity launched on default display and on top");
- final ImeEventStream stream = mockImeSession.openEventStream();
- expectEvent(stream, editorMatcher("onStartInput",
- imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
// Activity is no longer on the secondary display
assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isFalse();
+ // Tap on the imeTestActivity task center instead of the display center because
+ // the activity might not be spanning the entire display
+ final ImeEventStream stream = mockImeSession.openEventStream();
+ final WindowManagerState.Task testActivityTask = mWmState
+ .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName());
+ tapOnTaskCenter(testActivityTask);
+ expectEvent(stream, editorMatcher("onStartInput",
+ imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
+
// Verify the activity shows soft input on the default display.
showSoftInputAndAssertImeShownOnDisplay(DEFAULT_DISPLAY, imeTestActivitySession, stream);
}
@@ -721,10 +733,15 @@
.setShowSystemDecorations(true)
.setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
.setSimulateDisplay(true)
+ .setResizeDisplay(false)
.createDisplays(2);
final DisplayContent firstDisplay = newDisplays.get(0);
final DisplayContent secondDisplay = newDisplays.get(1);
+ // Skip if the test environment somehow didn't create 2 displays with identical size.
+ assumeTrue("Skip the test if the size of the created displays aren't identical",
+ firstDisplay.getDisplayRect().equals(secondDisplay.getDisplayRect()));
+
// Initialize IME test environment
final MockImeSession mockImeSession = createManagedMockImeSession(this);
final TestActivitySession<ImeTestActivity> imeTestActivitySession =
@@ -745,63 +762,73 @@
editorMatcher("onStartInput",
imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
event -> "showSoftInput".equals(event.getEventName()));
- // Launch Ime must not lead to screen size changes.
- waitAndAssertImeNoScreenSizeChanged(configChangeVerifyStream);
+ try {
+ // Launch Ime must not lead to screen size changes.
+ waitAndAssertImeNoScreenSizeChanged(configChangeVerifyStream);
- final Rect currentBoundsOnFirstDisplay = expectCommand(stream,
- mockImeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
- .getReturnParcelableValue();
+ final Rect currentBoundsOnFirstDisplay = expectCommand(stream,
+ mockImeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
+ .getReturnParcelableValue();
- // Clear onConfigurationChanged events before IME moves to the secondary display to prevent
- // flaky because IME may receive configuration updates which we don't care about.
- // An example is CONFIG_KEYBOARD_HIDDEN.
- configChangeVerifyStream = clearOnConfigurationChangedFromStream(stream);
+ // Clear onConfigurationChanged events before IME moves to the secondary display to
+ // prevent flaky because IME may receive configuration updates which we don't care
+ // about. An example is CONFIG_KEYBOARD_HIDDEN.
+ configChangeVerifyStream = clearOnConfigurationChangedFromStream(stream);
- // Tap secondDisplay to change it to the top focused display.
- tapOnDisplayCenter(secondDisplay.mId);
+ // Tap secondDisplay to change it to the top focused display.
+ tapOnDisplayCenter(secondDisplay.mId);
- // Move ImeTestActivity from firstDisplay to secondDisplay.
- getLaunchActivityBuilder()
- .setUseInstrumentation()
- .setTargetActivity(imeTestActivitySession.getActivity().getComponentName())
- .setIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .allowMultipleInstances(false)
- .setDisplayId(secondDisplay.mId).execute();
+ // Move ImeTestActivity from firstDisplay to secondDisplay.
+ getLaunchActivityBuilder()
+ .setUseInstrumentation()
+ .setTargetActivity(imeTestActivitySession.getActivity().getComponentName())
+ .setIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .allowMultipleInstances(false)
+ .setDisplayId(secondDisplay.mId).execute();
- // Make sure ImeTestActivity is move from the firstDisplay to the secondDisplay
- waitAndAssertTopResumedActivity(imeTestActivitySession.getActivity().getComponentName(),
- secondDisplay.mId, "ImeTestActivity must be top-resumed on display#"
- + secondDisplay.mId);
- assertThat(mWmState.hasActivityInDisplay(firstDisplay.mId,
- imeTestActivitySession.getActivity().getComponentName())).isFalse();
+ // Make sure ImeTestActivity is move from the firstDisplay to the secondDisplay
+ waitAndAssertTopResumedActivity(imeTestActivitySession.getActivity().getComponentName(),
+ secondDisplay.mId, "ImeTestActivity must be top-resumed on display#"
+ + secondDisplay.mId);
+ assertThat(mWmState.hasActivityInDisplay(firstDisplay.mId,
+ imeTestActivitySession.getActivity().getComponentName())).isFalse();
- // Show soft input again to trigger IME movement.
- imeTestActivitySession.runOnMainSyncAndWait(
- imeTestActivitySession.getActivity()::showSoftInput);
+ // Show soft input again to trigger IME movement.
+ imeTestActivitySession.runOnMainSyncAndWait(
+ imeTestActivitySession.getActivity()::showSoftInput);
- waitOrderedImeEventsThenAssertImeShown(stream, secondDisplay.mId,
- editorMatcher("onStartInput",
- imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
- event -> "showSoftInput".equals(event.getEventName()));
- // Moving IME to the display with the same display metrics must not lead to
- // screen size changes.
- waitAndAssertImeNoScreenSizeChanged(configChangeVerifyStream);
+ waitOrderedImeEventsThenAssertImeShown(stream, secondDisplay.mId,
+ editorMatcher("onStartInput",
+ imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
+ event -> "showSoftInput".equals(event.getEventName()));
- final Rect currentBoundsOnSecondDisplay = expectCommand(stream,
- mockImeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
- .getReturnParcelableValue();
+ // Moving IME to the display with the same display metrics must not lead to
+ // screen size changes.
+ waitAndAssertImeNoScreenSizeChanged(configChangeVerifyStream);
- assertWithMessage("The current WindowMetrics bounds of IME must not be changed.")
- .that(currentBoundsOnFirstDisplay).isEqualTo(currentBoundsOnSecondDisplay);
+ final Rect currentBoundsOnSecondDisplay = expectCommand(stream,
+ mockImeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
+ .getReturnParcelableValue();
+
+ assertWithMessage("The current WindowMetrics bounds of IME must not be changed.")
+ .that(currentBoundsOnFirstDisplay).isEqualTo(currentBoundsOnSecondDisplay);
+ } catch (AssertionError e) {
+ mWmState.computeState();
+ final Rect displayRect1 = mWmState.getDisplay(firstDisplay.mId).getDisplayRect();
+ final Rect displayRect2 = mWmState.getDisplay(secondDisplay.mId).getDisplayRect();
+ assumeTrue("Skip test since the size of one or both displays happens unexpected change",
+ displayRect1.equals(displayRect2));
+ throw e;
+ }
}
public static class ImeTestActivity extends Activity {
- ImeAwareEditText mEditText;
+ EditText mEditText;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
- mEditText = new ImeAwareEditText(this);
+ mEditText = new EditText(this);
// Set private IME option for editorMatcher to identify which TextView received
// onStartInput event.
resetPrivateImeOptionsIdentifier();
@@ -809,6 +836,9 @@
layout.setOrientation(LinearLayout.VERTICAL);
layout.addView(mEditText);
mEditText.requestFocus();
+ // SOFT_INPUT_STATE_UNSPECIFIED may produced unexpected behavior for CTS. To make tests
+ // deterministic, using SOFT_INPUT_STATE_UNCHANGED instead.
+ setUnchangedSoftInputState();
setContentView(layout);
}
@@ -821,6 +851,15 @@
mEditText.setPrivateImeOptions(
getClass().getName() + "/" + Long.toString(SystemClock.elapsedRealtimeNanos()));
}
+
+ private void setUnchangedSoftInputState() {
+ final Window window = getWindow();
+ final int currentSoftInputMode = window.getAttributes().softInputMode;
+ final int newSoftInputMode =
+ (currentSoftInputMode & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE)
+ | SOFT_INPUT_STATE_UNCHANGED;
+ window.setSoftInputMode(newSoftInputMode);
+ }
}
public static class ImeTestActivity2 extends ImeTestActivity { }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
index 98ad738..462e29f 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
@@ -350,7 +350,7 @@
return mObjectTracker.manage(new DisplayMetricsSession(displayId));
}
- public static class IgnoreOrientationRequestSession implements AutoCloseable {
+ public static class LetterboxAspectRatioSession implements AutoCloseable {
private static final String WM_SET_IGNORE_ORIENTATION_REQUEST =
"wm set-ignore-orientation-request ";
private static final String WM_GET_IGNORE_ORIENTATION_REQUEST =
@@ -358,31 +358,38 @@
private static final Pattern IGNORE_ORIENTATION_REQUEST_PATTERN =
Pattern.compile("ignoreOrientationRequest (true|false) for displayId=\\d+");
- final int mDisplayId;
- final boolean mInitialValue;
+ private static final String WM_SET_LETTERBOX_STYLE_ASPECT_RATIO =
+ "wm set-letterbox-style --aspectRatio ";
+ private static final String WM_RESET_LETTERBOX_STYLE_ASPECT_RATIO
+ = "wm reset-letterbox-style aspectRatio";
- IgnoreOrientationRequestSession(int displayId, boolean value) {
+ final int mDisplayId;
+ final boolean mInitialIgnoreOrientationRequest;
+
+ LetterboxAspectRatioSession(int displayId, float aspectRatio) {
mDisplayId = displayId;
Matcher matcher = IGNORE_ORIENTATION_REQUEST_PATTERN.matcher(
executeShellCommand(WM_GET_IGNORE_ORIENTATION_REQUEST + " -d " + mDisplayId));
assertTrue("get-ignore-orientation-request should match pattern", matcher.find());
- mInitialValue = Boolean.parseBoolean(matcher.group(1));
+ mInitialIgnoreOrientationRequest = Boolean.parseBoolean(matcher.group(1));
executeShellCommand("wm set-ignore-orientation-request true -d " + mDisplayId);
- executeShellCommand(WM_SET_IGNORE_ORIENTATION_REQUEST + value + " -d " + mDisplayId);
+ executeShellCommand(WM_SET_LETTERBOX_STYLE_ASPECT_RATIO + aspectRatio);
}
@Override
public void close() {
executeShellCommand(
- WM_SET_IGNORE_ORIENTATION_REQUEST + mInitialValue + " -d " + mDisplayId);
+ WM_SET_IGNORE_ORIENTATION_REQUEST + mInitialIgnoreOrientationRequest + " -d "
+ + mDisplayId);
+ executeShellCommand(WM_RESET_LETTERBOX_STYLE_ASPECT_RATIO);
}
}
/** @see ObjectTracker#manage(AutoCloseable) */
- protected IgnoreOrientationRequestSession createManagedIgnoreOrientationRequestSession(
- int displayId, boolean value) {
- return mObjectTracker.manage(new IgnoreOrientationRequestSession(displayId, value));
+ protected LetterboxAspectRatioSession createManagedLetterboxAspectRatioSession(int displayId,
+ float aspectRatio) {
+ return mObjectTracker.manage(new LetterboxAspectRatioSession(displayId, aspectRatio));
}
void waitForDisplayGone(Predicate<DisplayContent> displayPredicate) {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
index 74a6e3a9..13877bd 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -23,6 +23,8 @@
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.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.server.wm.CliIntentExtra.extraBool;
import static android.server.wm.CliIntentExtra.extraString;
import static android.server.wm.ComponentNameUtils.getActivityName;
@@ -105,8 +107,8 @@
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteCallback;
-import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.AsbSecurityTest;
+import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.server.wm.CommandSession.ActivityCallback;
import android.server.wm.CommandSession.SizeInfo;
@@ -148,11 +150,6 @@
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
@@ -249,10 +246,10 @@
public void testEnterPipToOtherOrientation() {
// Launch a portrait only app on the fullscreen stack
launchActivity(TEST_ACTIVITY,
- extraString(EXTRA_FIXED_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT)));
+ extraString(EXTRA_FIXED_ORIENTATION, String.valueOf(SCREEN_ORIENTATION_PORTRAIT)));
// Launch the PiP activity fixed as landscape
launchActivity(PIP_ACTIVITY,
- extraString(EXTRA_PIP_ORIENTATION, String.valueOf(ORIENTATION_LANDSCAPE)));
+ extraString(EXTRA_PIP_ORIENTATION, String.valueOf(SCREEN_ORIENTATION_LANDSCAPE)));
// Enter PiP, and assert that the PiP is within bounds now that the device is back in
// portrait
mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
@@ -453,7 +450,7 @@
public void testAutoEnterPictureInPictureOnUserLeaveHintWhenPipRequestedNotOverridden()
{
// Launch a test activity so that we're not over home
- launchActivity(TEST_ACTIVITY);
+ launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
// Launch the PIP activity that enters PIP on user leave hint, not on PIP requested
launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT, "true"));
@@ -1083,22 +1080,21 @@
assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest());
// Launch the PiP activity fixed as portrait, and enter picture-in-picture
launchActivity(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
- extraString(EXTRA_PIP_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT)),
+ extraString(EXTRA_PIP_ORIENTATION, String.valueOf(SCREEN_ORIENTATION_PORTRAIT)),
extraString(EXTRA_ENTER_PIP, "true"));
waitForEnterPip(PIP_ACTIVITY);
assertPinnedStackExists();
// Request that the orientation is set to landscape
- mBroadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE);
+ mBroadcastActionTrigger.requestOrientationForPip(SCREEN_ORIENTATION_LANDSCAPE);
// Launch the activity back into fullscreen and ensure that it is now in landscape
launchActivity(PIP_ACTIVITY);
waitForExitPipToFullscreen(PIP_ACTIVITY);
assertPinnedStackDoesNotExist();
- mWmState.waitForActivityOrientation(PIP_ACTIVITY, ORIENTATION_LANDSCAPE);
-
- final Task pipActivityTask = mWmState.getTaskByActivity(PIP_ACTIVITY);
- assertEquals(ORIENTATION_LANDSCAPE, pipActivityTask.mOverrideConfiguration.orientation);
+ assertTrue("The PiP activity in fullscreen must be landscape",
+ mWmState.waitForActivityOrientation(
+ PIP_ACTIVITY, Configuration.ORIENTATION_LANDSCAPE));
}
@Test
@@ -1252,7 +1248,7 @@
@Test
public void testDisplayMetricsPinUnpin() {
separateTestJournal();
- launchActivity(TEST_ACTIVITY);
+ launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
final int defaultWindowingMode = mWmState
.getTaskByActivity(TEST_ACTIVITY).getWindowingMode();
final SizeInfo initialSizes = getLastReportedSizesForActivity(TEST_ACTIVITY);
@@ -1286,14 +1282,14 @@
@Test
public void testAutoPipAllowedBypassesExplicitEnterPip() {
// Launch a test activity so that we're not over home.
- launchActivity(TEST_ACTIVITY);
+ launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
// Launch the PIP activity and set its pip params to allow auto-pip.
launchActivity(PIP_ACTIVITY, extraString(EXTRA_ALLOW_AUTO_PIP, "true"));
assertPinnedStackDoesNotExist();
// Launch a new activity and ensure that there is a pinned stack.
- launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
+ launchActivity(RESUME_WHILE_PAUSING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
waitForEnterPip(PIP_ACTIVITY);
assertPinnedStackExists();
waitAndAssertActivityState(PIP_ACTIVITY, STATE_PAUSED, "activity must be paused");
@@ -1306,7 +1302,7 @@
assertPinnedStackDoesNotExist();
// Launch another and ensure that there is a pinned stack.
- launchActivity(TEST_ACTIVITY);
+ launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
waitForEnterPip(PIP_ACTIVITY);
assertPinnedStackExists();
waitAndAssertActivityState(PIP_ACTIVITY, STATE_PAUSED, "activity must be paused");
@@ -1541,6 +1537,9 @@
return activity.getWindowingMode() == WINDOWING_MODE_PINNED
&& activity.getState().equals(STATE_PAUSED);
}, "checking activity windowing mode");
+ if (ENABLE_SHELL_TRANSITIONS) {
+ mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+ }
}
/**
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PrivacyIndicatorBoundsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/PrivacyIndicatorBoundsTests.java
index 49d2dba..c39aaa2 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PrivacyIndicatorBoundsTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PrivacyIndicatorBoundsTests.java
@@ -16,12 +16,13 @@
package android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
import static android.server.wm.RoundedCornerTests.TestActivity.EXTRA_ORIENTATION;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -29,14 +30,11 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import android.app.Activity;
-import android.app.AppOpsManager;
-import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
@@ -101,13 +99,24 @@
final View childWindowRoot = activity.getChildWindowRoot();
PollingCheck.waitFor(TIMEOUT_MS, () -> childWindowRoot.getWidth() > 0);
PollingCheck.waitFor(TIMEOUT_MS, () -> activity.getDispatchedInsets() != null);
+ mWmState.waitForValidState(mTestActivity.getActivity().getComponentName());
WindowInsets insets = activity.getDispatchedInsets();
assertNotNull(insets);
Rect screenBounds = activity.getScreenBounds();
assertNotNull(screenBounds);
Rect bounds = insets.getPrivacyIndicatorBounds();
assertNotNull(bounds);
- assertEquals(bounds.top, 0);
+ final int windowingMode = mWmState
+ .getTaskDisplayArea(mTestActivity.getActivity().getComponentName())
+ .getWindowingMode();
+ final boolean inMultiWindowMode = windowingMode != WINDOWING_MODE_FULLSCREEN
+ && windowingMode != WINDOWING_MODE_UNDEFINED;
+ if (!inMultiWindowMode) {
+ // Multi-window environments may place the indicator bounds somewhere other than the
+ // top (e.g. desktops may decide that the bottom-right corner has the highest visual
+ // priority). Other windowing modes
+ assertEquals(bounds.top, 0);
+ }
// TODO 188788786: Figure out why the screen bounds are different in cuttlefish,
// causing failures
// assertTrue(bounds + " not contained in " + screenBounds, screenBounds.contains(bounds));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java
index b49e726..4c03c1a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java
@@ -33,8 +33,11 @@
import static android.server.wm.app.Components.SPLASHSCREEN_ACTIVITY;
import static android.server.wm.app.Components.SPLASH_SCREEN_REPLACE_ICON_ACTIVITY;
import static android.server.wm.app.Components.SPLASH_SCREEN_REPLACE_THEME_ACTIVITY;
+import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITIES;
import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITY;
import static android.server.wm.app.Components.TestActivity.EXTRA_INTENT;
+import static android.server.wm.app.Components.TestActivity.EXTRA_INTENTS;
+import static android.server.wm.app.Components.TestActivity.EXTRA_OPTION;
import static android.server.wm.app.Components.TestStartingWindowKeys.CANCEL_HANDLE_EXIT;
import static android.server.wm.app.Components.TestStartingWindowKeys.CENTER_VIEW_IS_SURFACE_VIEW;
import static android.server.wm.app.Components.TestStartingWindowKeys.CONTAINS_BRANDING_VIEW;
@@ -67,6 +70,7 @@
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
+import android.app.ActivityOptions;
import android.app.UiModeManager;
import android.content.ComponentName;
import android.content.Intent;
@@ -82,6 +86,7 @@
import android.platform.test.annotations.Presubmit;
import android.view.WindowManager;
import android.view.WindowMetrics;
+import android.window.SplashScreen;
import androidx.core.graphics.ColorUtils;
@@ -131,6 +136,22 @@
private void startActivityFromTestLauncher(CommandSession.ActivitySession homeActivity,
ComponentName componentName, Consumer<Intent> fillExtra) {
+ startActivityFromTestLauncher(homeActivity, componentName, fillExtra, null /* options */);
+ }
+
+ private void startActivitiesFromTestLauncher(CommandSession.ActivitySession homeActivity,
+ Intent[] intents, ActivityOptions options) {
+
+ final Bundle data = new Bundle();
+ data.putParcelableArray(EXTRA_INTENTS, intents);
+ if (options != null) {
+ data.putParcelable(EXTRA_OPTION, options.toBundle());
+ }
+ homeActivity.sendCommand(COMMAND_START_ACTIVITIES, data);
+ }
+
+ private void startActivityFromTestLauncher(CommandSession.ActivitySession homeActivity,
+ ComponentName componentName, Consumer<Intent> fillExtra, ActivityOptions options) {
final Bundle data = new Bundle();
final Intent startIntent = new Intent();
@@ -138,6 +159,9 @@
startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
fillExtra.accept(startIntent);
data.putParcelable(EXTRA_INTENT, startIntent);
+ if (options != null) {
+ data.putParcelable(EXTRA_OPTION, options.toBundle());
+ }
homeActivity.sendCommand(COMMAND_START_ACTIVITY, data);
}
@@ -358,18 +382,9 @@
mWmState.computeState(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY);
mWmState.assertVisibility(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY, true);
- final TestJournalProvider.TestJournal journal =
- TestJournalProvider.TestJournalContainer.get(HANDLE_SPLASH_SCREEN_EXIT);
if (expectResult) {
- TestUtils.waitUntil("Waiting for runtime onSplashScreenExit", 5 /* timeoutSecond */,
- () -> journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
- assertTrue("No entry for CONTAINS_CENTER_VIEW",
- journal.extras.containsKey(CONTAINS_CENTER_VIEW));
- assertTrue("No entry for CONTAINS_BRANDING_VIEW",
- journal.extras.containsKey(CONTAINS_BRANDING_VIEW));
- assertTrue("Center View shouldn't be null", journal.extras.getBoolean(CONTAINS_CENTER_VIEW));
- assertTrue(journal.extras.getBoolean(CONTAINS_BRANDING_VIEW));
- assertEquals(Color.BLUE, journal.extras.getInt(ICON_BACKGROUND_COLOR, Color.YELLOW));
+ assertHandleExit(TestJournalProvider.TestJournalContainer
+ .get(HANDLE_SPLASH_SCREEN_EXIT));
}
}
@@ -433,17 +448,8 @@
});
mWmState.computeState(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY);
mWmState.assertVisibility(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, true);
- final TestJournalProvider.TestJournal journal =
- TestJournalProvider.TestJournalContainer.get(REPLACE_ICON_EXIT);
- TestUtils.waitUntil("Waiting for runtime onSplashScreenExit", 5 /* timeoutSecond */,
- () -> journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
- assertTrue(journal.extras.getBoolean(CONTAINS_CENTER_VIEW));
- final long iconAnimationStart = journal.extras.getLong(ICON_ANIMATION_START);
- final long iconAnimationDuration = journal.extras.getLong(ICON_ANIMATION_DURATION);
- assertTrue(iconAnimationStart != 0);
- assertEquals(iconAnimationDuration, 500);
- assertFalse(journal.extras.getBoolean(CONTAINS_BRANDING_VIEW));
- assertTrue(journal.extras.getBoolean(CENTER_VIEW_IS_SURFACE_VIEW));
+
+ assertReplaceIcon(TestJournalProvider.TestJournalContainer.get(REPLACE_ICON_EXIT));
}
@Test
@@ -512,6 +518,111 @@
}
@Test
+ public void testLaunchFromLauncherWithEmptyIconOptions() {
+ assumeFalse(isLeanBack());
+ final CommandSession.ActivitySession homeActivity = prepareTestLauncher();
+ TestJournalProvider.TestJournalContainer.start();
+ final ActivityOptions noIconOptions = ActivityOptions.makeBasic()
+ .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_EMPTY);
+ startActivityFromTestLauncher(homeActivity, SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, intent ->
+ intent.putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true), noIconOptions);
+ mWmState.waitForActivityState(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, STATE_RESUMED);
+ mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+
+ final TestJournalProvider.TestJournal journal =
+ TestJournalProvider.TestJournalContainer.get(REPLACE_ICON_EXIT);
+ assertFalse(journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
+ }
+
+ @Test
+ public void testLaunchAppWithIconOptions() throws Exception {
+ final Bundle bundle = ActivityOptions.makeBasic()
+ .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON).toBundle();
+ TestJournalProvider.TestJournalContainer.start();
+ final Intent intent = new Intent(Intent.ACTION_VIEW)
+ .setComponent(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true);
+ mContext.startActivity(intent, bundle);
+
+ mWmState.waitForActivityState(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY, STATE_RESUMED);
+ mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+
+ assertHandleExit(TestJournalProvider.TestJournalContainer.get(HANDLE_SPLASH_SCREEN_EXIT));
+ }
+
+ private void launchActivitiesFromLauncherWithOptions(Intent[] intents,
+ ActivityOptions options, ComponentName waitResumeComponent) {
+ assumeFalse(isLeanBack());
+ final CommandSession.ActivitySession homeActivity = prepareTestLauncher();
+ TestJournalProvider.TestJournalContainer.start();
+
+ startActivitiesFromTestLauncher(homeActivity, intents, options);
+
+ mWmState.waitForActivityState(waitResumeComponent, STATE_RESUMED);
+ mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testLaunchActivitiesWithIconOptions() throws Exception {
+ final ActivityOptions options = ActivityOptions.makeBasic()
+ .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+ final Intent[] intents = new Intent[] {
+ new Intent().setComponent(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+ new Intent().setComponent(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY)
+ .putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true)
+ };
+ launchActivitiesFromLauncherWithOptions(intents, options,
+ SPLASH_SCREEN_REPLACE_ICON_ACTIVITY);
+ assertReplaceIcon(TestJournalProvider.TestJournalContainer.get(REPLACE_ICON_EXIT));
+ }
+
+ @Test
+ public void testLaunchActivitiesWithEmptyOptions() {
+ final ActivityOptions options = ActivityOptions.makeBasic()
+ .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_EMPTY);
+
+ final Intent[] intents = new Intent[] {
+ new Intent().setComponent(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true),
+ new Intent().setComponent(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY)
+ .putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true)
+ };
+ launchActivitiesFromLauncherWithOptions(intents, options,
+ SPLASH_SCREEN_REPLACE_ICON_ACTIVITY);
+ final TestJournalProvider.TestJournal journal =
+ TestJournalProvider.TestJournalContainer.get(REPLACE_ICON_EXIT);
+ assertFalse(journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
+ }
+
+ private void assertReplaceIcon(TestJournalProvider.TestJournal journal) throws Exception {
+ TestUtils.waitUntil("Waiting for runtime onSplashScreenExit", 5 /* timeoutSecond */,
+ () -> journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
+ assertTrue(journal.extras.getBoolean(CONTAINS_CENTER_VIEW));
+ final long iconAnimationStart = journal.extras.getLong(ICON_ANIMATION_START);
+ final long iconAnimationDuration = journal.extras.getLong(ICON_ANIMATION_DURATION);
+ assertTrue(iconAnimationStart != 0);
+ assertEquals(iconAnimationDuration, 500);
+ assertFalse(journal.extras.getBoolean(CONTAINS_BRANDING_VIEW));
+ assertTrue(journal.extras.getBoolean(CENTER_VIEW_IS_SURFACE_VIEW));
+ }
+
+ private void assertHandleExit(TestJournalProvider.TestJournal journal) throws Exception {
+ TestUtils.waitUntil("Waiting for runtime onSplashScreenExit", 5 /* timeoutSecond */,
+ () -> journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
+ assertTrue("No entry for CONTAINS_CENTER_VIEW",
+ journal.extras.containsKey(CONTAINS_CENTER_VIEW));
+ assertTrue("No entry for CONTAINS_BRANDING_VIEW",
+ journal.extras.containsKey(CONTAINS_BRANDING_VIEW));
+ assertTrue("Center View shouldn't be null",
+ journal.extras.getBoolean(CONTAINS_CENTER_VIEW));
+ assertTrue(journal.extras.getBoolean(CONTAINS_BRANDING_VIEW));
+ assertEquals(Color.BLUE, journal.extras.getInt(ICON_BACKGROUND_COLOR, Color.YELLOW));
+ }
+
+ @Test
public void testOverrideSplashscreenTheme() {
assumeFalse(isLeanBack());
final CommandSession.ActivitySession homeActivity = prepareTestLauncher();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java b/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
index 7585856..9aed345 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
@@ -73,7 +73,10 @@
@Override
public void setUp() throws Exception {
super.setUp();
- mOwnerActivity = startActivity(ActivityA.class);
+ // Launch activities in fullscreen, otherwise, some tests fail on devices which use freeform
+ // as the default windowing mode, because tests' prerequisite are that activity A, B, and C
+ // need to overlay completely, but they can be partially overlay as freeform windows.
+ mOwnerActivity = startActivityInWindowingModeFullScreen(ActivityA.class);
mOwnerToken = getActivityToken(mOwnerActivity);
}
@@ -561,7 +564,7 @@
@Test
public void testTranslucentAdjacentTaskFragment() {
// Create ActivityB on top of ActivityA
- Activity activityB = startActivity(ActivityB.class);
+ Activity activityB = startActivityInWindowingModeFullScreen(ActivityB.class);
waitAndAssertResumedActivity(mActivityB, "Activity B must be resumed.");
waitAndAssertActivityState(mActivityA, STATE_STOPPED,
"Activity A is occluded by Activity B, so it must be stopped.");
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlTest.java b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlTest.java
index 9e00b08..84a1900 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlTest.java
@@ -78,7 +78,7 @@
}
private void verifyTest(SurfaceHolder.Callback callback, PixelChecker pixelChecker) {
- mActivity.verifyTest(callback, pixelChecker);
+ mActivity.verifyTest(callback, pixelChecker, mName);
}
@Before
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
index 12cc716..ade4a98 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
@@ -519,4 +519,57 @@
assertTrue(mClicked);
}
+ @Test
+ public void testCanReplaceSurfacePackage() throws Throwable {
+ // Create a surface view and wait for its surface to be created.
+ {
+ CountDownLatch surfaceCreated = new CountDownLatch(1);
+ mActivityRule.runOnUiThread(() -> {
+ final FrameLayout content = new FrameLayout(mActivity);
+ mSurfaceView = new SurfaceView(mActivity);
+ mSurfaceView.setZOrderOnTop(true);
+ content.addView(mSurfaceView, new FrameLayout.LayoutParams(
+ DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT,
+ Gravity.LEFT | Gravity.TOP));
+ mActivity.setContentView(content, new ViewGroup.LayoutParams(
+ DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT));
+ mSurfaceView.getHolder().addCallback(new SurfaceCreatedCallback(surfaceCreated));
+
+ // Create an embedded view without click handling.
+ mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
+ mSurfaceView.getHostToken());
+ mEmbeddedView = new Button(mActivity);
+ mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight);
+
+ });
+ surfaceCreated.await();
+ mSurfaceView.setChildSurfacePackage(mVr.getSurfacePackage());
+ mInstrumentation.waitForIdleSync();
+ waitUntilEmbeddedViewDrawn();
+ }
+
+ {
+ CountDownLatch hostReady = new CountDownLatch(1);
+ // Create a second surface view and wait for its surface to be created.
+ mActivityRule.runOnUiThread(() -> {
+ // Create an embedded view.
+ mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
+ mSurfaceView.getHostToken());
+ mEmbeddedView = new Button(mActivity);
+ mEmbeddedView.setOnClickListener((View v) -> mClicked = true);
+ mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight);
+ hostReady.countDown();
+
+ });
+ hostReady.await();
+ mSurfaceView.setChildSurfacePackage(mVr.getSurfacePackage());
+ mInstrumentation.waitForIdleSync();
+ waitUntilEmbeddedViewDrawn();
+ }
+
+ // Check to see if the click went through - this only would happen if the surface package
+ // was replaced
+ CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
+ assertTrue(mClicked);
+ }
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceViewTest.java b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceViewTest.java
index 767650d..7e15846 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceViewTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceViewTest.java
@@ -39,6 +39,7 @@
import com.android.compatibility.common.util.CtsKeyEventUtil;
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.Before;
import org.junit.Rule;
@@ -61,7 +62,7 @@
public void setup() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mActivity = mActivityRule.getActivity();
- PollingCheck.waitFor(mActivity::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity);
mMockSurfaceView = mActivity.getSurfaceView();
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ToastWindowTest.java b/tests/framework/base/windowmanager/src/android/server/wm/ToastWindowTest.java
index db90054..4741f39 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ToastWindowTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ToastWindowTest.java
@@ -19,10 +19,7 @@
import static android.server.wm.app.Components.ClickableToastActivity.ACTION_TOAST_DISPLAYED;
import static android.server.wm.app.Components.ClickableToastActivity.ACTION_TOAST_TAP_DETECTED;
-import static com.google.common.truth.Truth.assertThat;
-
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -92,7 +89,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_TOAST_DISPLAYED);
filter.addAction(ACTION_TOAST_TAP_DETECTED);
- mContext.registerReceiver(mAppCommunicator, filter);
+ mContext.registerReceiver(mAppCommunicator, filter, Context.RECEIVER_EXPORTED);
}
@After
@@ -134,7 +131,7 @@
WindowState toastWindow = wmState.findFirstWindowWithType(LayoutParams.TYPE_TOAST);
assertNotNull("Couldn't retrieve toast window", toastWindow);
- tapOnCenter(toastWindow.getContainingFrame(), toastWindow.getDisplayId());
+ tapOnCenter(toastWindow.getParentFrame(), toastWindow.getDisplayId());
boolean toastClicked = getBroadcastReceivedVariable(ACTION_TOAST_TAP_DETECTED).block(
TOAST_TAP_TIMEOUT_MS);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
index 8e28903..20bce66 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
@@ -62,6 +62,7 @@
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.Rule;
import org.junit.Test;
@@ -96,7 +97,7 @@
try (ImeSession imeSession = new ImeSession(SimpleIme.getName(mContext))) {
TestActivity activity = launchActivity();
activity.setUseControlApi(useControlApi);
- PollingCheck.waitFor(activity::hasWindowFocus);
+ WindowUtil.waitForFocus(activity);
activity.setEvaluator(() -> {
// This runs from time to time on the UI thread.
Bitmap screenshot = getInstrumentation().getUiAutomation().takeScreenshot();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
index 054da4c..0953fc5 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
@@ -63,13 +63,10 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewParent;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
-import android.view.WindowInsetsController;
import android.view.WindowManager;
-import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -521,7 +518,8 @@
@Test
public void testHideOnCreate() throws Exception {
- final TestHideOnCreateActivity activity = startActivity(TestHideOnCreateActivity.class);
+ final TestHideOnCreateActivity activity =
+ startActivityInWindowingModeFullScreen(TestHideOnCreateActivity.class);
final View rootView = activity.getWindow().getDecorView();
ANIMATION_CALLBACK.waitForFinishing();
PollingCheck.waitFor(TIMEOUT,
@@ -641,7 +639,8 @@
@Test
public void testWindowInsetsController_availableAfterAddView() throws Exception {
- final TestHideOnCreateActivity activity = startActivity(TestHideOnCreateActivity.class);
+ final TestHideOnCreateActivity activity =
+ startActivityInWindowingModeFullScreen(TestHideOnCreateActivity.class);
final View rootView = activity.getWindow().getDecorView();
ANIMATION_CALLBACK.waitForFinishing();
PollingCheck.waitFor(TIMEOUT,
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
index ab057d7..806cee5 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
@@ -17,6 +17,7 @@
package android.server.wm;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.provider.Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS;
import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
@@ -38,6 +39,8 @@
import android.graphics.Insets;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.server.wm.settings.SettingsSession;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -47,10 +50,11 @@
import androidx.test.rule.ActivityTestRule;
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
import org.hamcrest.CustomTypeSafeMatcher;
import org.hamcrest.Matcher;
+import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
@@ -65,6 +69,8 @@
private ComponentName mTestActivityComponentName;
+ private SettingsSession<String> mImmersiveModeConfirmationSetting;
+
@Rule
public final ErrorCollector mErrorCollector = new ErrorCollector();
@@ -93,6 +99,18 @@
public void setUp() throws Exception {
super.setUp();
mTestActivityComponentName = new ComponentName(mContext, TestActivity.class);
+ mImmersiveModeConfirmationSetting = new SettingsSession<>(
+ Settings.Secure.getUriFor(IMMERSIVE_MODE_CONFIRMATIONS),
+ Settings.Secure::getString, Settings.Secure::putString);
+ mImmersiveModeConfirmationSetting.set("confirmed");
+
+ }
+
+ @After
+ public void tearDown() {
+ if (mImmersiveModeConfirmationSetting != null) {
+ mImmersiveModeConfirmationSetting.close();
+ }
}
@Test
@@ -259,7 +277,7 @@
private <T extends Activity> T launchAndWait(ActivityTestRule<T> rule) {
final T activity = rule.launchActivity(null);
- PollingCheck.waitFor(activity::hasWindowFocus);
+ WindowUtil.waitForFocus(activity);
return activity;
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
index 7571a56..22ef041 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
@@ -32,7 +32,6 @@
import static android.server.wm.app.Components.SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY;
import static android.server.wm.app.Components.TEST_ACTIVITY;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
-import static android.view.Display.DEFAULT_DISPLAY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -40,22 +39,15 @@
import static org.junit.Assume.assumeTrue;
import android.app.Activity;
-import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
import android.server.wm.ActivityLauncher;
-import android.server.wm.WindowManagerState;
import android.server.wm.app.Components;
-import android.util.Log;
-import android.view.WindowManager;
import org.junit.Test;
-import java.util.List;
-import java.util.function.Predicate;
-
/**
* Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:ActivityStarterTests
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
index 1c2fdf9..56cadf7 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
@@ -2499,7 +2499,8 @@
String amStartCmd =
(mWindowingMode == -1 || mNewTask)
? getAmStartCmd(mLaunchingActivity)
- : getAmStartCmd(mLaunchingActivity, mWindowingMode);
+ : getAmStartCmd(mLaunchingActivity, mDisplayId)
+ + " --windowingMode " + mWindowingMode;
// Use launching activity to launch the target.
commandBuilder.append(amStartCmd)
.append(" -f 0x20000020");
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java b/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java
index b56b748..a98f546 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java
@@ -403,7 +403,8 @@
mThread = new HandlerThread(mClientId);
mThread.start();
context.registerReceiver(this, new IntentFilter(mClientId),
- null /* broadcastPermission */, new Handler(mThread.getLooper()));
+ null /* broadcastPermission */, new Handler(mThread.getLooper()),
+ Context.RECEIVER_EXPORTED);
}
/** Start the activity by the given intent and wait it becomes idle. */
@@ -558,7 +559,7 @@
mHostId = hostId;
mClientId = clientId;
mCallback = callback;
- context.registerReceiver(this, new IntentFilter(hostId));
+ context.registerReceiver(this, new IntentFilter(hostId), Context.RECEIVER_EXPORTED);
}
@Override
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
index 2308932..fd21f5b 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
@@ -45,7 +45,6 @@
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.util.SparseArray;
-import android.view.WindowManager;
import android.view.nano.DisplayInfoProto;
import android.view.nano.ViewProtoEnums;
@@ -1095,6 +1094,10 @@
return mFocusedDisplayId;
}
+ public boolean isFixedToUserRotation() {
+ return getDisplay(DEFAULT_DISPLAY).mIsFixedToUserRotation;
+ }
+
public static class DisplayContent extends DisplayArea {
public int mId;
ArrayList<Task> mRootTasks = new ArrayList<>();
@@ -1118,6 +1121,7 @@
private int mUserRotation;
private int mFixedToUserRotationMode;
private int mLastOrientation;
+ private boolean mIsFixedToUserRotation;
DisplayContent(DisplayContentProto proto) {
super(proto.rootDisplayArea);
@@ -1164,6 +1168,7 @@
mUserRotation = rotationProto.userRotation;
mFixedToUserRotationMode = rotationProto.fixedToUserRotationMode;
mLastOrientation = rotationProto.lastOrientation;
+ mIsFixedToUserRotation = rotationProto.isFixedToUserRotation;
}
}
@@ -1288,6 +1293,64 @@
return "Display #" + mId + ": name=" + mName + " mDisplayRect=" + mDisplayRect
+ " mAppRect=" + mAppRect + " mFlags=" + mFlags;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o == null) {
+ return false;
+ }
+ if (!(o instanceof DisplayContent)) {
+ return false;
+ }
+
+ DisplayContent dc = (DisplayContent) o;
+
+ return (dc.mDisplayRect == null ? mDisplayRect == null
+ : dc.mDisplayRect.equals(mDisplayRect))
+ && (dc.mAppRect == null ? mAppRect == null : dc.mAppRect.equals(mAppRect))
+ && dc.mDpi == mDpi
+ && dc.mFlags == mFlags
+ && (dc.mName == null ? mName == null : dc.mName.equals(mName))
+ && dc.mSurfaceSize == mSurfaceSize
+ && (dc.mAppTransitionState == null ? mAppTransitionState == null
+ : dc.mAppTransitionState.equals(mAppTransitionState))
+ && dc.mRotation == mRotation
+ && dc.mFrozenToUserRotation == mFrozenToUserRotation
+ && dc.mUserRotation == mUserRotation
+ && dc.mFixedToUserRotationMode == mFixedToUserRotationMode
+ && dc.mLastOrientation == mLastOrientation
+ && dc.mIsFixedToUserRotation == mIsFixedToUserRotation;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ if (mDisplayRect != null) {
+ result = 31 * result + mDisplayRect.hashCode();
+ }
+ if (mAppRect != null) {
+ result = 31 * result + mAppRect.hashCode();
+ }
+ result = 31 * result + mDpi;
+ result = 31 * result + mFlags;
+ if (mName != null) {
+ result = 31 * result + mName.hashCode();
+ }
+ result = 31 * result + mSurfaceSize;
+ if (mAppTransitionState != null) {
+ result = 31 * result + mAppTransitionState.hashCode();
+ }
+ result = 31 * result + mRotation;
+ result = 31 * result + Boolean.hashCode(mFrozenToUserRotation);
+ result = 31 * result + mUserRotation;
+ result = 31 * result + mFixedToUserRotationMode;
+ result = 31 * result + mLastOrientation;
+ result = 31 * result + Boolean.hashCode(mIsFixedToUserRotation);
+ return result;
+ }
}
public static class Task extends ActivityContainer {
@@ -1656,9 +1719,9 @@
if (proto != null) {
aodShowing = proto.aodShowing;
keyguardShowing = proto.keyguardShowing;
- for (int i = 0; i < proto.keyguardOccludedStates.length; i++) {
- mKeyguardOccludedStates.append(proto.keyguardOccludedStates[i].displayId,
- proto.keyguardOccludedStates[i].keyguardOccluded);
+ for (int i = 0; i < proto.keyguardPerDisplay.length; i++) {
+ mKeyguardOccludedStates.append(proto.keyguardPerDisplay[i].displayId,
+ proto.keyguardPerDisplay[i].keyguardOccluded);
}
}
}
@@ -1904,12 +1967,11 @@
private int mStackId;
private int mLayer;
private boolean mShown;
- private Rect mContainingFrame;
private Rect mParentFrame;
private Rect mFrame;
private Rect mCompatFrame;
- private Rect mSurfaceInsets = new Rect();
- private Rect mGivenContentInsets = new Rect();
+ private Rect mSurfaceInsets;
+ private Rect mGivenContentInsets;
private Rect mCrop = new Rect();
private boolean mHasCompatScale;
private float mGlobalScale;
@@ -1936,7 +1998,6 @@
WindowFramesProto windowFramesProto = proto.windowFrames;
if (windowFramesProto != null) {
mFrame = extract(windowFramesProto.frame);
- mContainingFrame = extract(windowFramesProto.containingFrame);
mParentFrame = extract(windowFramesProto.parentFrame);
mCompatFrame = extract(windowFramesProto.compatFrame);
}
@@ -1980,10 +2041,6 @@
return mStackId;
}
- Rect getContainingFrame() {
- return mContainingFrame;
- }
-
public Rect getFrame() {
return mFrame;
}
@@ -2050,7 +2107,7 @@
public String toString() {
return "WindowState: {" + mAppToken + " " + mName
+ getWindowTypeSuffix(mWindowType) + "}" + " type=" + mType
- + " cf=" + mContainingFrame + " pf=" + mParentFrame;
+ + " pf=" + mParentFrame;
}
public String toLongString() {
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
index 0f0e512..42f5e92 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
@@ -227,16 +227,13 @@
}
/**
- * Wait for orientation for the Activity
+ * Wait for the configuration orientation of the Activity.
*/
- public void waitForActivityOrientation(ComponentName activityName, int orientation) {
- waitForWithAmState(amState -> {
- final Task task = amState.getTaskByActivity(activityName);
- if (task == null) {
- return false;
- }
- return task.mFullConfiguration.orientation == orientation;
- }, "orientation of " + getActivityName(activityName) + " to be " + orientation);
+ public boolean waitForActivityOrientation(ComponentName activityName, int configOrientation) {
+ return waitForWithAmState(amState -> {
+ final Activity activity = amState.getActivity(activityName);
+ return activity != null && activity.mFullConfiguration.orientation == configOrientation;
+ }, "orientation of " + getActivityName(activityName) + " to be " + configOrientation);
}
public void waitForDisplayUnfrozen() {
@@ -320,7 +317,7 @@
}, windowName + "'s surface is disappeared");
}
- void waitAndAssertWindowSurfaceShown(String windowName, boolean shown) {
+ public void waitAndAssertWindowSurfaceShown(String windowName, boolean shown) {
assertTrue(
waitForWithAmState(state -> state.isWindowSurfaceShown(windowName) == shown,
windowName + "'s isWindowSurfaceShown to return " + shown));
diff --git a/tests/input/AndroidManifest.xml b/tests/input/AndroidManifest.xml
index f019c8f..666ec20 100644
--- a/tests/input/AndroidManifest.xml
+++ b/tests/input/AndroidManifest.xml
@@ -39,11 +39,14 @@
<activity android:name="android.input.cts.IncompleteMotionActivity"
android:label="IncompleteMotion activity"
- android:turnScreenOn="true">
+ android:turnScreenOn="true"
+ android:exported="true">
</activity>
<activity android:name="android.input.cts.CaptureEventActivity"
android:label="Capture events"
- android:turnScreenOn="true">
+ android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|uiMode|navigation|keyboard|density|fontScale|layoutDirection|locale|mcc|mnc|smallestScreenSize"
+ android:turnScreenOn="true"
+ android:exported="true">
</activity>
</application>
diff --git a/tests/input/res/raw/test_keyboard_register.json b/tests/input/res/raw/test_keyboard_register.json
new file mode 100644
index 0000000..bc2e4a4
--- /dev/null
+++ b/tests/input/res/raw/test_keyboard_register.json
@@ -0,0 +1,14 @@
+{
+ "id": 1,
+ "type": "uinput",
+ "command": "register",
+ "name": "Test Keyboard (USB)",
+ "vid": 0x18d1,
+ "pid": 0xabcd,
+ "bus": "usb",
+ "configuration":[
+ {"type": 100, "data": [1]}, // UI_SET_EVBIT : EV_KEY
+ // UI_SET_KEYBIT : KEY_Q, KEY_W, KEY_E, KEY_A, KEY_B, KEY_C
+ {"type": 101, "data": [16, 17, 18, 30, 48, 46]}
+ ]
+}
diff --git a/tests/input/src/android/input/cts/AppKeyCombinationsTest.kt b/tests/input/src/android/input/cts/AppKeyCombinationsTest.kt
new file mode 100644
index 0000000..145c36f
--- /dev/null
+++ b/tests/input/src/android/input/cts/AppKeyCombinationsTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.input.cts
+
+import android.view.KeyEvent
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.PollingCheck
+import com.android.compatibility.common.util.ShellUtils
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.Assert.assertEquals
+
+/**
+ * Tests for the common key combinations should be consumed by app first.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AppKeyCombinationsTest {
+ @get:Rule
+ var activityRule = ActivityScenarioRule(CaptureEventActivity::class.java)
+ private lateinit var activity: CaptureEventActivity
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ @Before
+ fun setUp() {
+ activityRule.getScenario().onActivity {
+ activity = it
+ }
+ PollingCheck.waitFor { activity.hasWindowFocus() }
+ instrumentation.uiAutomation.syncInputTransactions()
+ }
+
+ @Test
+ fun testCtrlSpace() {
+ ShellUtils.runShellCommand("input keycombination CTRL_LEFT SPACE")
+ assertKeyEvent(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.ACTION_DOWN, 0)
+ assertKeyEvent(KeyEvent.KEYCODE_SPACE, KeyEvent.ACTION_DOWN, META_CTRL)
+ assertKeyEvent(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.ACTION_UP, META_CTRL)
+ assertKeyEvent(KeyEvent.KEYCODE_SPACE, KeyEvent.ACTION_UP, 0)
+ }
+
+ @Test
+ fun testCtrlAltZ() {
+ ShellUtils.runShellCommand("input keycombination CTRL_LEFT ALT_LEFT Z")
+ assertKeyEvent(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.ACTION_DOWN, 0)
+ assertKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.ACTION_DOWN, META_CTRL)
+ assertKeyEvent(KeyEvent.KEYCODE_Z, KeyEvent.ACTION_DOWN, META_CTRL or META_ALT)
+ assertKeyEvent(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.ACTION_UP, META_CTRL or META_ALT)
+ assertKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.ACTION_UP, META_ALT)
+ assertKeyEvent(KeyEvent.KEYCODE_Z, KeyEvent.ACTION_UP, 0)
+ }
+
+ private fun assertKeyEvent(keyCode: Int, action: Int, metaState: Int) {
+ val event = activity.getInputEvent() as KeyEvent
+ assertEquals(keyCode, event.keyCode)
+ assertEquals(action, event.action)
+ assertEquals(0, event.flags)
+ assertEquals(metaState, event.metaState)
+ }
+
+ companion object {
+ const val META_CTRL = KeyEvent.META_CTRL_ON or KeyEvent.META_CTRL_LEFT_ON
+ const val META_ALT = KeyEvent.META_ALT_ON or KeyEvent.META_ALT_LEFT_ON
+ }
+}
diff --git a/tests/input/src/android/input/cts/CaptureEventActivity.kt b/tests/input/src/android/input/cts/CaptureEventActivity.kt
index 4f5caad..73c911d 100644
--- a/tests/input/src/android/input/cts/CaptureEventActivity.kt
+++ b/tests/input/src/android/input/cts/CaptureEventActivity.kt
@@ -24,29 +24,29 @@
import java.util.concurrent.TimeUnit
class CaptureEventActivity : Activity() {
- private val mEvents = LinkedBlockingQueue<InputEvent>()
+ private val events = LinkedBlockingQueue<InputEvent>()
override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
- mEvents.add(MotionEvent.obtain(ev))
+ events.add(MotionEvent.obtain(ev))
return true
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
- mEvents.add(MotionEvent.obtain(ev))
+ events.add(MotionEvent.obtain(ev))
return true
}
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
- mEvents.add(KeyEvent(event))
+ events.add(KeyEvent(event))
return true
}
override fun dispatchTrackballEvent(ev: MotionEvent?): Boolean {
- mEvents.add(MotionEvent.obtain(ev))
+ events.add(MotionEvent.obtain(ev))
return true
}
- fun getLastInputEvent(): InputEvent? {
- return mEvents.poll(5, TimeUnit.SECONDS)
+ fun getInputEvent(): InputEvent? {
+ return events.poll(5, TimeUnit.SECONDS)
}
}
diff --git a/tests/input/src/android/input/cts/GamepadWithAccessibilityTest.kt b/tests/input/src/android/input/cts/GamepadWithAccessibilityTest.kt
index 4b8adaa..7fbac02 100644
--- a/tests/input/src/android/input/cts/GamepadWithAccessibilityTest.kt
+++ b/tests/input/src/android/input/cts/GamepadWithAccessibilityTest.kt
@@ -22,11 +22,12 @@
import android.view.InputDevice
import android.view.KeyCharacterMap.VIRTUAL_KEYBOARD
import android.view.KeyEvent
+import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.rule.ActivityTestRule
import com.android.compatibility.common.util.PollingCheck
+import com.android.cts.input.UinputDevice
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
@@ -37,12 +38,9 @@
import org.junit.Test
import org.junit.runner.RunWith
-import com.android.cts.input.InputJsonParser
-import com.android.cts.input.UinputDevice
-
private fun setTouchExplorationEnabled(instrumentation: Instrumentation, enabled: Boolean) {
val TIMEOUT_FOR_SERVICE_ENABLE_MILLIS: Long = 10_000 // 10s
- var manager: AccessibilityManager =
+ val manager: AccessibilityManager =
instrumentation.getTargetContext().getSystemService(AccessibilityManager::class.java)
val uiAutomation = instrumentation.getUiAutomation()
@@ -70,21 +68,22 @@
@RunWith(AndroidJUnit4::class)
class GamepadWithAccessibilityTest {
@get:Rule
- var mActivityRule: ActivityTestRule<CaptureEventActivity> =
- ActivityTestRule(CaptureEventActivity::class.java)
- lateinit var mActivity: CaptureEventActivity
- val mInstrumentation = InstrumentationRegistry.getInstrumentation()
+ val activityRule = ActivityScenarioRule(CaptureEventActivity::class.java)
+ private lateinit var activity: CaptureEventActivity
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
@Before
fun setUp() {
- mActivity = mActivityRule.getActivity()
- PollingCheck.waitFor { mActivity.hasWindowFocus() }
- setTouchExplorationEnabled(mInstrumentation, true)
+ activityRule.getScenario().onActivity {
+ activity = it
+ }
+ PollingCheck.waitFor { activity.hasWindowFocus() }
+ setTouchExplorationEnabled(instrumentation, true)
}
@After
fun tearDown() {
- setTouchExplorationEnabled(mInstrumentation, false)
+ setTouchExplorationEnabled(instrumentation, false)
}
/**
@@ -93,14 +92,8 @@
*/
@Test
fun testDeviceId() {
- val resourceId = R.raw.google_gamepad_register
-
- val parser = InputJsonParser(mInstrumentation.getTargetContext())
- val resourceDeviceId = parser.readDeviceId(resourceId)
- val registerCommand = parser.readRegisterCommand(resourceId)
- val uInputDevice = UinputDevice(mInstrumentation, resourceDeviceId,
- parser.readVendorId(resourceId), parser.readProductId(resourceId),
- InputDevice.SOURCE_KEYBOARD, registerCommand)
+ val uinputDevice = UinputDevice.create(instrumentation, R.raw.google_gamepad_register,
+ InputDevice.SOURCE_KEYBOARD)
val EV_SYN = 0
val SYN_REPORT = 0
@@ -109,14 +102,14 @@
val EV_KEY_UP = 0
val BTN_GAMEPAD = 0x130
val evdevEventsDown = intArrayOf(EV_KEY, BTN_GAMEPAD, EV_KEY_DOWN, EV_SYN, SYN_REPORT, 0)
- uInputDevice.injectEvents(evdevEventsDown.joinToString(prefix = "[", postfix = "]",
+ uinputDevice.injectEvents(evdevEventsDown.joinToString(prefix = "[", postfix = "]",
separator = ","))
val evdevEventsUp = intArrayOf(EV_KEY, BTN_GAMEPAD, EV_KEY_UP, EV_SYN, SYN_REPORT, 0)
- uInputDevice.injectEvents(evdevEventsUp.joinToString(prefix = "[", postfix = "]",
+ uinputDevice.injectEvents(evdevEventsUp.joinToString(prefix = "[", postfix = "]",
separator = ","))
- val lastInputEvent = mActivity.getLastInputEvent()
+ val lastInputEvent = activity.getInputEvent()
assertNotNull(lastInputEvent)
assertTrue(lastInputEvent is KeyEvent)
val keyEvent = lastInputEvent as KeyEvent
@@ -124,6 +117,6 @@
// KeyEvent.FLAG_IS_ACCESSIBILITY_EVENT in getFlags()
assertEquals(KeyEvent.FLAG_FROM_SYSTEM, keyEvent.getFlags())
assertNotEquals(keyEvent.getDeviceId(), VIRTUAL_KEYBOARD)
- assertEquals(keyEvent.getDeviceId(), uInputDevice.getDeviceId())
+ assertEquals(keyEvent.getDeviceId(), uinputDevice.getDeviceId())
}
}
diff --git a/tests/input/src/android/input/cts/IncompleteMotionActivity.kt b/tests/input/src/android/input/cts/IncompleteMotionActivity.kt
index ac7cc3b..706619d 100644
--- a/tests/input/src/android/input/cts/IncompleteMotionActivity.kt
+++ b/tests/input/src/android/input/cts/IncompleteMotionActivity.kt
@@ -21,15 +21,15 @@
import java.util.concurrent.atomic.AtomicBoolean
class IncompleteMotionActivity : Activity() {
- private val mReceivedMove = AtomicBoolean(false)
+ private val receivedMove = AtomicBoolean(false)
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_MOVE) {
- mReceivedMove.set(true)
+ receivedMove.set(true)
}
return true
}
fun receivedMove(): Boolean {
- return mReceivedMove.get()
+ return receivedMove.get()
}
}
diff --git a/tests/input/src/android/input/cts/IncompleteMotionTest.kt b/tests/input/src/android/input/cts/IncompleteMotionTest.kt
index 26ae69a..d39958e 100644
--- a/tests/input/src/android/input/cts/IncompleteMotionTest.kt
+++ b/tests/input/src/android/input/cts/IncompleteMotionTest.kt
@@ -28,10 +28,10 @@
import android.view.MotionEvent.ACTION_DOWN
import android.view.MotionEvent.ACTION_MOVE
import android.view.View
+import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.rule.ActivityTestRule
import com.android.compatibility.common.util.PollingCheck
import org.junit.Before
import org.junit.Rule
@@ -54,13 +54,13 @@
* When OverlayActivity receives focus, it will send out the OVERLAY_ACTIVITY_FOCUSED broadcast.
*/
class OverlayFocusedBroadcastReceiver : BroadcastReceiver() {
- private val mIsFocused = AtomicBoolean(false)
+ private val isFocused = AtomicBoolean(false)
override fun onReceive(context: Context, intent: Intent) {
- mIsFocused.set(true)
+ isFocused.set(true)
}
fun overlayActivityIsFocused(): Boolean {
- return mIsFocused.get()
+ return isFocused.get()
}
}
@@ -78,15 +78,16 @@
@RunWith(AndroidJUnit4::class)
class IncompleteMotionTest {
@get:Rule
- var mActivityRule: ActivityTestRule<IncompleteMotionActivity> =
- ActivityTestRule(IncompleteMotionActivity::class.java)
- lateinit var mActivity: IncompleteMotionActivity
- val mInstrumentation = InstrumentationRegistry.getInstrumentation()
+ val activityRule = ActivityScenarioRule(IncompleteMotionActivity::class.java)
+ private lateinit var activity: IncompleteMotionActivity
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
@Before
fun setUp() {
- mActivity = mActivityRule.getActivity()
- PollingCheck.waitFor { mActivity.hasWindowFocus() }
+ activityRule.getScenario().onActivity {
+ activity = it
+ }
+ PollingCheck.waitFor { activity.hasWindowFocus() }
}
/**
@@ -95,13 +96,13 @@
@Test
fun testIncompleteMotion() {
val downTime = SystemClock.uptimeMillis()
- val (x, y) = getViewCenterOnScreen(mActivity.window.decorView)
+ val (x, y) = getViewCenterOnScreen(activity.window.decorView)
// Start a valid touch stream
sendEvent(downTime, ACTION_DOWN, x, y, true /*sync*/)
// Lock up the UI thread. This ensures that the motion event that we will write will
// not get processed by the app right away.
- mActivity.runOnUiThread {
+ activity.runOnUiThread {
val sendMoveAndFocus = thread(start = true) {
sendEvent(downTime, ACTION_MOVE, x, y + 10, false /*sync*/)
// The MOVE event is sent async because the UI thread is blocked.
@@ -114,12 +115,12 @@
val handler = Handler(looper)
val receiver = OverlayFocusedBroadcastReceiver()
val intentFilter = IntentFilter(OVERLAY_ACTIVITY_FOCUSED)
- mActivity.registerReceiver(receiver, intentFilter, null, handler)
+ activity.registerReceiver(receiver, intentFilter, null, handler)
// Now send hasFocus=false event to the app by launching a new focusable window
startOverlayActivity()
PollingCheck.waitFor { receiver.overlayActivityIsFocused() }
- mActivity.unregisterReceiver(receiver)
+ activity.unregisterReceiver(receiver)
handlerThread.quit()
// We need to ensure that the focus event has been written to the app's socket
// before unblocking the UI thread. Having the overlay activity receive
@@ -130,14 +131,14 @@
}
sendMoveAndFocus.join()
}
- PollingCheck.waitFor { !mActivity.hasWindowFocus() }
+ PollingCheck.waitFor { !activity.hasWindowFocus() }
// If the platform implementation has a bug, it would consume both MOVE and FOCUS events,
// but will only call 'finish' for the focus event.
// The MOVE event would not be propagated to the app, because the Choreographer
// callback never gets scheduled
// If we wait too long here, we will cause ANR (if the platform has a bug).
// If the MOVE event is received, however, we can stop the test.
- PollingCheck.waitFor { mActivity.receivedMove() }
+ PollingCheck.waitFor { activity.receivedMove() }
}
private fun sendEvent(downTime: Long, action: Int, x: Float, y: Float, sync: Boolean) {
@@ -147,7 +148,7 @@
}
val event = MotionEvent.obtain(downTime, eventTime, action, x, y, 0 /*metaState*/)
event.source = InputDevice.SOURCE_TOUCHSCREEN
- mInstrumentation.uiAutomation.injectInputEvent(event, sync)
+ instrumentation.uiAutomation.injectInputEvent(event, sync)
}
/**
@@ -161,6 +162,6 @@
private fun startOverlayActivity() {
val flags = " -W -n "
val startCmd = "am start $flags android.input.cts/.OverlayActivity"
- mInstrumentation.uiAutomation.executeShellCommand(startCmd)
+ instrumentation.uiAutomation.executeShellCommand(startCmd)
}
}
diff --git a/tests/input/src/android/input/cts/InputEventTest.kt b/tests/input/src/android/input/cts/InputEventTest.kt
new file mode 100644
index 0000000..fd20ac2
--- /dev/null
+++ b/tests/input/src/android/input/cts/InputEventTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.input.cts
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+
+import android.text.TextUtils
+import android.util.ArrayMap
+import android.view.KeyEvent
+import android.view.MotionEvent
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InputEventTest {
+
+ @Test
+ fun testKeyCodeToString() {
+ assertEquals("KEYCODE_UNKNOWN", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_UNKNOWN))
+ assertEquals("KEYCODE_HOME", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_HOME))
+ assertEquals("KEYCODE_0", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_0))
+ assertEquals("KEYCODE_POWER", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_POWER))
+ assertEquals("KEYCODE_A", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_A))
+ assertEquals("KEYCODE_SPACE", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_SPACE))
+ assertEquals("KEYCODE_MENU", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_MENU))
+ assertEquals("KEYCODE_BACK", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_BACK))
+ assertEquals("KEYCODE_BUTTON_A", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_BUTTON_A))
+ assertEquals("KEYCODE_PROFILE_SWITCH",
+ KeyEvent.keyCodeToString(KeyEvent.KEYCODE_PROFILE_SWITCH))
+ }
+
+ @Test
+ fun testAxisFromToString() {
+ val axes = ArrayMap<Int, String>()
+ axes.put(MotionEvent.AXIS_X, "AXIS_X")
+ axes.put(MotionEvent.AXIS_Y, "AXIS_Y")
+ axes.put(MotionEvent.AXIS_PRESSURE, "AXIS_PRESSURE")
+ axes.put(MotionEvent.AXIS_SIZE, "AXIS_SIZE")
+ axes.put(MotionEvent.AXIS_TOUCH_MAJOR, "AXIS_TOUCH_MAJOR")
+ axes.put(MotionEvent.AXIS_TOUCH_MINOR, "AXIS_TOUCH_MINOR")
+ axes.put(MotionEvent.AXIS_TOOL_MAJOR, "AXIS_TOOL_MAJOR")
+ axes.put(MotionEvent.AXIS_TOOL_MINOR, "AXIS_TOOL_MINOR")
+ axes.put(MotionEvent.AXIS_ORIENTATION, "AXIS_ORIENTATION")
+ axes.put(MotionEvent.AXIS_VSCROLL, "AXIS_VSCROLL")
+ axes.put(MotionEvent.AXIS_HSCROLL, "AXIS_HSCROLL")
+ axes.put(MotionEvent.AXIS_Z, "AXIS_Z")
+ axes.put(MotionEvent.AXIS_RX, "AXIS_RX")
+ axes.put(MotionEvent.AXIS_RY, "AXIS_RY")
+ axes.put(MotionEvent.AXIS_RZ, "AXIS_RZ")
+ axes.put(MotionEvent.AXIS_HAT_X, "AXIS_HAT_X")
+ axes.put(MotionEvent.AXIS_HAT_Y, "AXIS_HAT_Y")
+ axes.put(MotionEvent.AXIS_LTRIGGER, "AXIS_LTRIGGER")
+ axes.put(MotionEvent.AXIS_RTRIGGER, "AXIS_RTRIGGER")
+ axes.put(MotionEvent.AXIS_THROTTLE, "AXIS_THROTTLE")
+ axes.put(MotionEvent.AXIS_RUDDER, "AXIS_RUDDER")
+ axes.put(MotionEvent.AXIS_WHEEL, "AXIS_WHEEL")
+ axes.put(MotionEvent.AXIS_GAS, "AXIS_GAS")
+ axes.put(MotionEvent.AXIS_BRAKE, "AXIS_BRAKE")
+ axes.put(MotionEvent.AXIS_DISTANCE, "AXIS_DISTANCE")
+ axes.put(MotionEvent.AXIS_TILT, "AXIS_TILT")
+ axes.put(MotionEvent.AXIS_SCROLL, "AXIS_SCROLL")
+ axes.put(MotionEvent.AXIS_RELATIVE_X, "AXIS_RELATIVE_X")
+ axes.put(MotionEvent.AXIS_RELATIVE_Y, "AXIS_RELATIVE_Y")
+ axes.put(MotionEvent.AXIS_GENERIC_1, "AXIS_GENERIC_1")
+ axes.put(MotionEvent.AXIS_GENERIC_2, "AXIS_GENERIC_2")
+ axes.put(MotionEvent.AXIS_GENERIC_3, "AXIS_GENERIC_3")
+ axes.put(MotionEvent.AXIS_GENERIC_4, "AXIS_GENERIC_4")
+ axes.put(MotionEvent.AXIS_GENERIC_5, "AXIS_GENERIC_5")
+ axes.put(MotionEvent.AXIS_GENERIC_6, "AXIS_GENERIC_6")
+ axes.put(MotionEvent.AXIS_GENERIC_7, "AXIS_GENERIC_7")
+ axes.put(MotionEvent.AXIS_GENERIC_8, "AXIS_GENERIC_8")
+ axes.put(MotionEvent.AXIS_GENERIC_9, "AXIS_GENERIC_9")
+ axes.put(MotionEvent.AXIS_GENERIC_10, "AXIS_GENERIC_10")
+ axes.put(MotionEvent.AXIS_GENERIC_11, "AXIS_GENERIC_11")
+ axes.put(MotionEvent.AXIS_GENERIC_12, "AXIS_GENERIC_12")
+ axes.put(MotionEvent.AXIS_GENERIC_13, "AXIS_GENERIC_13")
+ axes.put(MotionEvent.AXIS_GENERIC_14, "AXIS_GENERIC_14")
+ axes.put(MotionEvent.AXIS_GENERIC_15, "AXIS_GENERIC_15")
+ axes.put(MotionEvent.AXIS_GENERIC_16, "AXIS_GENERIC_16")
+ // As Axes values definition is not continuous from AXIS_RELATIVE_Y to AXIS_GENERIC_1,
+ // Need to verify MotionEvent.axisToString returns axis name correctly.
+ // Also verify that we are not crashing on those calls, and that the return result on each
+ // is not empty. We do expect the two-way call chain of to/from to get us back to the
+ // original integer value.
+ for (entry in axes.entries) {
+ val axis = entry.key
+ val axisToString = MotionEvent.axisToString(axis)
+ assertFalse(TextUtils.isEmpty(axisToString))
+ assertEquals(axisToString, entry.value)
+ assertEquals(axis, MotionEvent.axisFromString(axisToString))
+ }
+ }
+}
diff --git a/tests/input/src/android/input/cts/InputShellCommandTest.kt b/tests/input/src/android/input/cts/InputShellCommandTest.kt
index 4555b5d..c075204 100644
--- a/tests/input/src/android/input/cts/InputShellCommandTest.kt
+++ b/tests/input/src/android/input/cts/InputShellCommandTest.kt
@@ -17,10 +17,10 @@
import android.view.MotionEvent
import android.view.View
+import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.rule.ActivityTestRule
import com.android.compatibility.common.util.PollingCheck
import com.android.compatibility.common.util.ShellUtils
import com.google.common.truth.Truth.assertThat
@@ -44,15 +44,17 @@
@RunWith(AndroidJUnit4::class)
class InputShellCommandTest {
@get:Rule
- var mActivityRule: ActivityTestRule<CaptureEventActivity> =
- ActivityTestRule(CaptureEventActivity::class.java)
- lateinit var mActivity: CaptureEventActivity
- val mInstrumentation = InstrumentationRegistry.getInstrumentation()
+ val activityRule = ActivityScenarioRule(CaptureEventActivity::class.java)
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private lateinit var activity: CaptureEventActivity
@Before
fun setUp() {
- mActivity = mActivityRule.getActivity()
- PollingCheck.waitFor { mActivity.hasWindowFocus() }
+ activityRule.getScenario().onActivity {
+ activity = it
+ }
+ PollingCheck.waitFor { activity.hasWindowFocus() }
+ instrumentation.uiAutomation.syncInputTransactions()
}
/**
@@ -60,7 +62,7 @@
*/
@Test
fun testDefaultToolType() {
- val (x, y) = getViewCenterOnScreen(mActivity.window.decorView)
+ val (x, y) = getViewCenterOnScreen(activity.window.decorView)
ShellUtils.runShellCommand("input tap $x $y")
assertTapToolType(MotionEvent.TOOL_TYPE_FINGER)
@@ -71,7 +73,7 @@
*/
@Test
fun testToolType() {
- val (x, y) = getViewCenterOnScreen(mActivity.window.decorView)
+ val (x, y) = getViewCenterOnScreen(activity.window.decorView)
ShellUtils.runShellCommand("input touchscreen tap $x $y")
assertTapToolType(MotionEvent.TOOL_TYPE_FINGER)
@@ -96,7 +98,7 @@
}
private fun getMotionEvent(): MotionEvent {
- val event = mActivity.getLastInputEvent()
+ val event = activity.getInputEvent()
assertThat(event).isNotNull()
assertThat(event).isInstanceOf(MotionEvent::class.java)
return event as MotionEvent
@@ -111,12 +113,12 @@
}
private fun assertTapToolType(toolType: Int) {
- var event = getMotionEvent()
- assertThat(event.action).isEqualTo(MotionEvent.ACTION_DOWN)
- assertToolType(event, toolType)
+ val downEvent = getMotionEvent()
+ assertThat(downEvent.action).isEqualTo(MotionEvent.ACTION_DOWN)
+ assertToolType(downEvent, toolType)
- event = getMotionEvent()
- assertThat(event.action).isEqualTo(MotionEvent.ACTION_UP)
- assertToolType(event, toolType)
+ val upEvent = getMotionEvent()
+ assertThat(upEvent.action).isEqualTo(MotionEvent.ACTION_UP)
+ assertToolType(upEvent, toolType)
}
}
diff --git a/tests/input/src/android/input/cts/PointerCancelTest.kt b/tests/input/src/android/input/cts/PointerCancelTest.kt
new file mode 100644
index 0000000..af7ca56
--- /dev/null
+++ b/tests/input/src/android/input/cts/PointerCancelTest.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.input.cts
+
+import android.graphics.PointF
+import android.os.SystemClock
+import android.view.Gravity
+import android.view.InputDevice
+import android.view.InputEvent
+import android.view.MotionEvent
+import android.view.View
+import android.view.WindowManager
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.PollingCheck
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+
+private fun getViewCenterOnScreen(v: View): PointF {
+ val location = IntArray(2)
+ v.getLocationOnScreen(location)
+ val x = location[0].toFloat() + v.width / 2
+ val y = location[1].toFloat() + v.height / 2
+ return PointF(x, y)
+}
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class PointerCancelTest {
+ @get:Rule
+ val activityRule = ActivityScenarioRule<CaptureEventActivity>(CaptureEventActivity::class.java)
+ private lateinit var activity: CaptureEventActivity
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private lateinit var verifier: EventVerifier
+ @Before
+ fun setUp() {
+ activityRule.getScenario().onActivity {
+ activity = it
+ }
+ PollingCheck.waitFor { activity.hasWindowFocus() }
+ instrumentation.uiAutomation.syncInputTransactions()
+ verifier = EventVerifier(activity::getInputEvent)
+ }
+
+ /**
+ * Check that pointer cancel is received by the activity via injectInputEvent.
+ */
+ @Test
+ fun testPointerCancelMotion() {
+ val downTime = SystemClock.uptimeMillis()
+ val pointerInDecorView = getViewCenterOnScreen(activity.window.decorView)
+
+ // Start a valid touch stream
+ sendEvent(downTime, MotionEvent.ACTION_DOWN, pointerInDecorView, true /*sync*/)
+ verifier.assertReceivedDown()
+
+ pointerInDecorView.offset(0f, 1f)
+ sendEvent(downTime, MotionEvent.ACTION_MOVE, pointerInDecorView, true /*sync*/)
+ verifier.assertReceivedMove()
+
+ val secondPointer = PointF(pointerInDecorView.x + 1, pointerInDecorView.y + 1)
+ sendPointersEvent(downTime, ACTION_POINTER_1_DOWN,
+ arrayOf(pointerInDecorView, secondPointer),
+ 0 /*flags*/, true /*sync*/)
+ verifier.assertReceivedPointerDown()
+
+ sendPointersEvent(downTime, ACTION_POINTER_1_UP,
+ arrayOf(pointerInDecorView, secondPointer),
+ MotionEvent.FLAG_CANCELED, true /*sync*/)
+ verifier.assertReceivedPointerCancel()
+
+ sendEvent(downTime, MotionEvent.ACTION_UP, pointerInDecorView, true /*sync*/)
+ verifier.assertReceivedUp()
+ }
+
+ @Test
+ fun testPointerCancelForSplitTouch() {
+ val view = addFloatingWindow()
+ val pointerInFloating = getViewCenterOnScreen(view)
+ val downTime = SystemClock.uptimeMillis()
+ val pointerOutsideFloating = PointF(pointerInFloating.x + view.width / 2 + 1,
+ pointerInFloating.y + view.height / 2 + 1)
+
+ val eventsInFloating = LinkedBlockingQueue<InputEvent>()
+ view.setOnTouchListener { v, event ->
+ eventsInFloating.add(MotionEvent.obtain(event))
+ }
+ val verifierForFloating = EventVerifier { eventsInFloating.poll(5, TimeUnit.SECONDS) }
+
+ // First finger down (floating window)
+ sendEvent(downTime, MotionEvent.ACTION_DOWN, pointerInFloating, true /*sync*/)
+ verifierForFloating.assertReceivedDown()
+
+ // First finger move (floating window)
+ pointerInFloating.offset(0f, 1f)
+ sendEvent(downTime, MotionEvent.ACTION_MOVE, pointerInFloating, true /*sync*/)
+ verifierForFloating.assertReceivedMove()
+
+ // Second finger down (activity window)
+ sendPointersEvent(downTime, ACTION_POINTER_1_DOWN,
+ arrayOf(pointerInFloating, pointerOutsideFloating),
+ 0 /*flags*/, true /*sync*/)
+ verifier.assertReceivedDown()
+ verifierForFloating.assertReceivedMove()
+
+ // ACTION_CANCEL with cancel flag (activity window)
+ sendPointersEvent(downTime, ACTION_POINTER_1_UP,
+ arrayOf(pointerInFloating, pointerOutsideFloating),
+ MotionEvent.FLAG_CANCELED, true /*sync*/)
+ verifier.assertReceivedCancel()
+ verifierForFloating.assertReceivedMove()
+
+ // First finger up (floating window)
+ sendEvent(downTime, MotionEvent.ACTION_UP, pointerInFloating, true /*sync*/)
+ verifierForFloating.assertReceivedUp()
+ }
+
+ private fun sendEvent(downTime: Long, action: Int, pt: PointF, sync: Boolean) {
+ val eventTime = when (action) {
+ MotionEvent.ACTION_DOWN -> downTime
+ else -> SystemClock.uptimeMillis()
+ }
+ val event = MotionEvent.obtain(downTime, eventTime, action, pt.x, pt.y, 0 /*metaState*/)
+ event.source = InputDevice.SOURCE_TOUCHSCREEN
+ instrumentation.uiAutomation.injectInputEvent(event, sync)
+ }
+
+ private fun sendPointersEvent(
+ downTime: Long,
+ action: Int,
+ pointers: Array<PointF>,
+ flags: Int,
+ sync: Boolean
+ ) {
+ val eventTime = SystemClock.uptimeMillis()
+ val pointerCount = pointers.size
+ val properties = arrayOfNulls<MotionEvent.PointerProperties>(pointerCount)
+ val coords = arrayOfNulls<MotionEvent.PointerCoords>(pointerCount)
+
+ for (i in 0 until pointerCount) {
+ properties[i] = MotionEvent.PointerProperties()
+ properties[i]!!.id = i
+ properties[i]!!.toolType = MotionEvent.TOOL_TYPE_FINGER
+ coords[i] = MotionEvent.PointerCoords()
+ coords[i]!!.x = pointers[i].x
+ coords[i]!!.y = pointers[i].y
+ }
+
+ val event = MotionEvent.obtain(downTime, eventTime, action, pointerCount,
+ properties, coords, 0 /*metaState*/, 0 /*buttonState*/,
+ 0f /*xPrecision*/, 0f /*yPrecision*/, 0 /*deviceId*/, 0 /*edgeFlags*/,
+ InputDevice.SOURCE_TOUCHSCREEN, flags)
+ instrumentation.uiAutomation.injectInputEvent(event, sync)
+ }
+
+ private fun addFloatingWindow(): View {
+ val view = View(instrumentation.targetContext)
+ val layoutParams = WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
+ layoutParams.x = 0
+ layoutParams.y = 0
+ layoutParams.width = 100
+ layoutParams.height = 100
+ layoutParams.gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
+
+ activity.runOnUiThread {
+ view.setBackgroundColor(android.graphics.Color.RED)
+ activity.windowManager.addView(view, layoutParams)
+ }
+
+ PollingCheck.waitFor {
+ view.hasWindowFocus()
+ }
+ instrumentation.uiAutomation.syncInputTransactions()
+ return view
+ }
+
+ inner class EventVerifier(val getInputEvent: () -> InputEvent?) {
+ fun assertReceivedPointerCancel() {
+ val event = getInputEvent() as MotionEvent
+ assertEquals(MotionEvent.ACTION_POINTER_UP, event.actionMasked)
+ assertEquals(MotionEvent.FLAG_CANCELED, event.flags and MotionEvent.FLAG_CANCELED)
+ }
+
+ fun assertReceivedCancel() {
+ val event = getInputEvent() as MotionEvent
+ assertEquals(MotionEvent.ACTION_CANCEL, event.actionMasked)
+ assertEquals(MotionEvent.FLAG_CANCELED, event.flags and MotionEvent.FLAG_CANCELED)
+ }
+
+ fun assertReceivedDown() {
+ val event = getInputEvent() as MotionEvent
+ assertEquals(MotionEvent.ACTION_DOWN, event.actionMasked)
+ }
+
+ fun assertReceivedPointerDown() {
+ val event = getInputEvent() as MotionEvent
+ assertEquals(MotionEvent.ACTION_POINTER_DOWN, event.actionMasked)
+ }
+
+ fun assertReceivedMove() {
+ val event = getInputEvent() as MotionEvent
+ assertEquals(MotionEvent.ACTION_MOVE, event.actionMasked)
+ }
+
+ fun assertReceivedUp() {
+ val event = getInputEvent() as MotionEvent
+ assertEquals(MotionEvent.ACTION_UP, event.actionMasked)
+ }
+ }
+
+ companion object {
+ val ACTION_POINTER_1_DOWN = (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT) or
+ MotionEvent.ACTION_POINTER_DOWN
+ val ACTION_POINTER_1_UP = (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT) or
+ MotionEvent.ACTION_POINTER_UP
+ }
+}
diff --git a/tests/input/src/android/input/cts/VerifyHardwareKeyEventTest.kt b/tests/input/src/android/input/cts/VerifyHardwareKeyEventTest.kt
new file mode 100644
index 0000000..b6f5a3c
--- /dev/null
+++ b/tests/input/src/android/input/cts/VerifyHardwareKeyEventTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.input.cts
+
+import android.hardware.input.InputManager
+import android.view.InputDevice
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.PollingCheck
+import com.android.cts.input.UinputDevice
+import org.junit.Assert.assertNotNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private fun injectEvents(device: UinputDevice, events: IntArray) {
+ device.injectEvents(events.joinToString(prefix = "[", postfix = "]", separator = ","))
+}
+
+/**
+ * Create a virtual keyboard and inject a 'hardware' key event. Ensure that the event can be
+ * verified using the InputManager::verifyInputEvent api.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class VerifyHardwareKeyEventTest {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ @get:Rule
+ val rule = ActivityScenarioRule<CaptureEventActivity>(CaptureEventActivity::class.java)
+
+ private lateinit var activity: CaptureEventActivity
+ private lateinit var inputManager: InputManager
+
+ @Before
+ fun setUp() {
+ rule.getScenario().onActivity {
+ inputManager = it.getSystemService(InputManager::class.java)
+ activity = it
+ }
+ PollingCheck.waitFor { activity.hasWindowFocus() }
+ }
+
+ fun assertReceivedEventsCanBeVerified(numEvents: Int) {
+ for (i in 1..numEvents) {
+ val lastInputEvent = activity.getInputEvent()
+ assertNotNull("Event number $i is null!", lastInputEvent)
+ assertNotNull(inputManager.verifyInputEvent(lastInputEvent!!))
+ }
+ }
+
+ /**
+ * Send a hardware key event and check that InputManager::verifyInputEvent returns non-null
+ * result.
+ */
+ @Test
+ fun testVerifyHardwareKeyEvent() {
+ val keyboardDevice = UinputDevice.create(instrumentation, R.raw.test_keyboard_register,
+ InputDevice.SOURCE_KEYBOARD)
+
+ val EV_SYN = 0
+ val SYN_REPORT = 0
+ val EV_KEY = 1
+ val EV_KEY_DOWN = 1
+ val EV_KEY_UP = 0
+ val KEY_A = 30
+
+ injectEvents(keyboardDevice, intArrayOf(EV_KEY, KEY_A, EV_KEY_DOWN, EV_SYN, SYN_REPORT, 0))
+ // Send the UP event right away to avoid key repeat
+ injectEvents(keyboardDevice, intArrayOf(EV_KEY, KEY_A, EV_KEY_UP, EV_SYN, SYN_REPORT, 0))
+
+ assertReceivedEventsCanBeVerified(2 /*numEvents*/)
+ }
+}
diff --git a/tests/inputmethod/Android.bp b/tests/inputmethod/Android.bp
index ceb6d02..4902be2 100644
--- a/tests/inputmethod/Android.bp
+++ b/tests/inputmethod/Android.bp
@@ -27,12 +27,15 @@
compile_multilib: "both",
libs: ["android.test.runner"],
static_libs: [
+ "androidx.test.ext.junit",
"androidx.test.rules",
"androidx.test.uiautomator_uiautomator",
"compatibility-device-util-axt",
+ "cts-wm-util",
"ctstestrunner-axt",
"CtsMockInputMethodLib",
"CtsMockSpellCheckerLib",
+ "CtsLegacyImeClientTestLib",
"testng",
"kotlin-test",
],
diff --git a/tests/inputmethod/legacyimeclienttestlib/Android.bp b/tests/inputmethod/legacyimeclienttestlib/Android.bp
new file mode 100644
index 0000000..59d7782
--- /dev/null
+++ b/tests/inputmethod/legacyimeclienttestlib/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_helper_library {
+ name: "CtsLegacyImeClientTestLib",
+
+ // Ideally we want to use SDK version 3, but it's not available.
+ // Note that several new APIs were added into InputConnection in API 9.
+ sdk_version: "4",
+
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "androidx.annotation_annotation",
+ ],
+}
diff --git a/tests/inputmethod/legacyimeclienttestlib/src/com/android/cts/inputmethod/LegacyImeClientTestUtils.java b/tests/inputmethod/legacyimeclienttestlib/src/com/android/cts/inputmethod/LegacyImeClientTestUtils.java
new file mode 100644
index 0000000..2c4a0da
--- /dev/null
+++ b/tests/inputmethod/legacyimeclienttestlib/src/com/android/cts/inputmethod/LegacyImeClientTestUtils.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.inputmethod;
+
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+
+import androidx.annotation.AnyThread;
+
+/**
+ * A utility class to test compatibility with legacy apps that were built with old SDKs.
+ */
+public final class LegacyImeClientTestUtils {
+
+ /**
+ * Not intented to be instantiated.
+ */
+ private LegacyImeClientTestUtils() {
+ }
+
+ private static final class MinimallyImplementedNoOpInputConnection implements InputConnection {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public CharSequence getTextBeforeCursor(int n, int flags) {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public CharSequence getTextAfterCursor(int n, int flags) {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getCursorCapsMode(int reqModes) {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean finishComposingText() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean commitCompletion(CompletionInfo text) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean setSelection(int start, int end) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean performEditorAction(int editorAction) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean performContextMenuAction(int id) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean beginBatchEdit() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean endBatchEdit() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean sendKeyEvent(KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean clearMetaKeyStates(int states) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean reportFullscreenMode(boolean enabled) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean performPrivateCommand(String action, Bundle data) {
+ return false;
+ }
+ }
+
+ /**
+ * @return an instance of {@link InputConnection} that implements only methods that were
+ * available in {@link android.os.Build.VERSION_CODES#CUPCAKE}.
+ */
+ @AnyThread
+ public static InputConnection createMinimallyImplementedNoOpInputConnection() {
+ return new MinimallyImplementedNoOpInputConnection();
+ }
+}
diff --git a/tests/inputmethod/mockime/res/xml/method.xml b/tests/inputmethod/mockime/res/xml/method.xml
index 48f4a78..6757eeb 100644
--- a/tests/inputmethod/mockime/res/xml/method.xml
+++ b/tests/inputmethod/mockime/res/xml/method.xml
@@ -17,5 +17,6 @@
<input-method xmlns:android="http://schemas.android.com/apk/res/android"
android:supportsInlineSuggestions="true"
- android:configChanges="fontScale">
+ android:configChanges="fontScale"
+ android:supportsStylusHandwriting="true">
</input-method>
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java
index abfae95..6c2ced4 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java
@@ -21,6 +21,7 @@
import android.os.Handler;
import android.os.Parcelable;
import android.view.View;
+import android.view.inputmethod.TextSnapshot;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -63,6 +64,9 @@
if (object instanceof Handler) {
return ReturnType.KnownUnsupportedType;
}
+ if (object instanceof TextSnapshot) {
+ return ReturnType.KnownUnsupportedType;
+ }
if (object instanceof Boolean) {
return ReturnType.Boolean;
}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
index bf301d6..cb49460 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
@@ -18,6 +18,7 @@
import android.os.SystemClock;
import android.text.TextUtils;
+import android.util.Pair;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
@@ -332,6 +333,63 @@
}
/**
+ * Assert that the {@link MockIme} will not be terminated abruptly with executing a command to
+ * check if it's still alive and verify the number of create/destroy callback should be paired.
+ *
+ * @param session {@link MockImeSession} to be checked.
+ * @param timeout timeout in millisecond to check if {@link MockIme} is still alive.
+ * @throws Exception
+ */
+ public static void expectNoImeCrash(@NonNull MockImeSession session, long timeout)
+ throws Exception {
+ // Issue any trivial command to make sure that the MockIme is still alive.
+ final ImeCommand command = session.callGetDisplayId();
+ expectCommand(session.openEventStream(), command, timeout);
+ // A filter that matches exit events of "onCreate", "onDestroy", and the *command* above.
+ final Predicate<ImeEvent> matcher = event -> {
+ if (!event.isEnterEvent()) {
+ return false;
+ }
+ switch (event.getEventName()) {
+ case "onHandleCommand": {
+ final ImeCommand eventCommand =
+ ImeCommand.fromBundle(event.getArguments().getBundle("command"));
+ return eventCommand.getId() == command.getId();
+ }
+ case "onCreate":
+ case "onDestroy":
+ return true;
+ default:
+ return false;
+ }
+ };
+ final ImeEventStream stream = session.openEventStream();
+ String lastEventName = null;
+ // Allowed pairs of (lastEventName, eventName):
+ // - (null, "onCreate")
+ // - ("onCreate", "onDestroy")
+ // - ("onCreate", "onHandleCommand") -> then stop searching
+ // - ("onDestroy", "onCreate")
+ while (true) {
+ final String eventName =
+ stream.seekToFirst(matcher).map(ImeEvent::getEventName).orElse("");
+ final Pair<String, String> pair = Pair.create(lastEventName, eventName);
+ if (pair.equals(Pair.create("onCreate", "onHandleCommand"))) {
+ break; // Done!
+ }
+ if (pair.equals(Pair.create(null, "onCreate"))
+ || pair.equals(Pair.create("onCreate", "onDestroy"))
+ || pair.equals(Pair.create("onDestroy", "onCreate"))) {
+ lastEventName = eventName;
+ stream.skip(1);
+ continue;
+ }
+ throw new AssertionError("IME might have crashed. lastEventName="
+ + lastEventName + " eventName=" + eventName + "\n" + stream.dump());
+ }
+ }
+
+ /**
* Waits until {@code MockIme} does not send {@code "onInputViewLayoutChanged"} event
* for a certain period of time ({@code stableThresholdTime} msec).
*
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
index 5757750..d2a8743 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
@@ -16,13 +16,18 @@
package com.android.cts.mockime;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
import android.os.Bundle;
import android.os.PersistableBundle;
import androidx.annotation.ColorInt;
+import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.lang.annotation.Retention;
+
/**
* An immutable data store to control the behavior of {@link MockIme}.
*/
@@ -44,7 +49,7 @@
private static final String DRAWS_BEHIND_NAV_BAR = "drawsBehindNavBar";
private static final String WINDOW_FLAGS = "WindowFlags";
private static final String WINDOW_FLAGS_MASK = "WindowFlagsMask";
- private static final String FULLSCREEN_MODE_ALLOWED = "FullscreenModeAllowed";
+ private static final String FULLSCREEN_MODE_POLICY = "FullscreenModePolicy";
private static final String INPUT_VIEW_SYSTEM_UI_VISIBILITY = "InputViewSystemUiVisibility";
private static final String WATERMARK_ENABLED = "WatermarkEnabled";
private static final String HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED =
@@ -58,6 +63,40 @@
@NonNull
private final PersistableBundle mBundle;
+
+ @Retention(SOURCE)
+ @IntDef(value = {
+ FullscreenModePolicy.NO_FULLSCREEN,
+ FullscreenModePolicy.FORCE_FULLSCREEN,
+ FullscreenModePolicy.OS_DEFAULT,
+ })
+ public @interface FullscreenModePolicy {
+ /**
+ * Let {@link MockIme} always return {@code false} from
+ * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}.
+ *
+ * <p>This is chosen to be the default behavior of {@link MockIme} to make CTS tests most
+ * deterministic.</p>
+ */
+ int NO_FULLSCREEN = 0;
+
+ /**
+ * Let {@link MockIme} always return {@code true} from
+ * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}.
+ *
+ * <p>This can be used to test the behaviors when a full-screen IME is running.</p>
+ */
+ int FORCE_FULLSCREEN = 1;
+
+ /**
+ * Let {@link MockIme} always return the default behavior of
+ * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}.
+ *
+ * <p>This can be used to test the default behavior of that public API.</p>
+ */
+ int OS_DEFAULT = 2;
+ }
+
ImeSettings(@NonNull String clientPackageName, @NonNull Bundle bundle) {
mClientPackageName = clientPackageName;
mEventCallbackActionName = bundle.getString(EVENT_CALLBACK_INTENT_ACTION_KEY);
@@ -74,8 +113,9 @@
return mClientPackageName;
}
- public boolean fullscreenModeAllowed(boolean defaultValue) {
- return mBundle.getBoolean(FULLSCREEN_MODE_ALLOWED, defaultValue);
+ @FullscreenModePolicy
+ public int fullscreenModePolicy() {
+ return mBundle.getInt(FULLSCREEN_MODE_POLICY);
}
@ColorInt
@@ -152,15 +192,14 @@
private final PersistableBundle mBundle = new PersistableBundle();
/**
- * Controls whether fullscreen mode is allowed or not.
+ * Controls how MockIme reacts to
+ * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}.
*
- * <p>By default, fullscreen mode is not allowed in {@link MockIme}.</p>
- *
- * @param allowed {@code true} if fullscreen mode is allowed
+ * @param policy one of {@link FullscreenModePolicy}
* @see MockIme#onEvaluateFullscreenMode()
*/
- public Builder setFullscreenModeAllowed(boolean allowed) {
- mBundle.putBoolean(FULLSCREEN_MODE_ALLOWED, allowed);
+ public Builder setFullscreenModePolicy(@FullscreenModePolicy int policy) {
+ mBundle.putInt(FULLSCREEN_MODE_POLICY, policy);
return this;
}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
index 342ab91..2373c79 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -64,6 +64,7 @@
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.TextAttribute;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
@@ -210,6 +211,13 @@
final int flag = command.getExtras().getInt("flag");
return getMemorizedOrCurrentInputConnection().getSelectedText(flag);
}
+ case "getSurroundingText": {
+ final int beforeLength = command.getExtras().getInt("beforeLength");
+ final int afterLength = command.getExtras().getInt("afterLength");
+ final int flags = command.getExtras().getInt("flags");
+ return getMemorizedOrCurrentInputConnection().getSurroundingText(
+ beforeLength, afterLength, flags);
+ }
case "getCursorCapsMode": {
final int reqModes = command.getExtras().getInt("reqModes");
return getMemorizedOrCurrentInputConnection().getCursorCapsMode(reqModes);
@@ -233,28 +241,54 @@
return getMemorizedOrCurrentInputConnection()
.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
}
- case "setComposingText": {
+ case "setComposingText(CharSequence,int)": {
final CharSequence text = command.getExtras().getCharSequence("text");
final int newCursorPosition =
command.getExtras().getInt("newCursorPosition");
return getMemorizedOrCurrentInputConnection().setComposingText(
text, newCursorPosition);
}
- case "setComposingRegion": {
+ case "setComposingText(CharSequence,int,TextAttribute)": {
+ final CharSequence text = command.getExtras().getCharSequence("text");
+ final int newCursorPosition =
+ command.getExtras().getInt("newCursorPosition");
+ final TextAttribute textAttribute =
+ command.getExtras().getParcelable("textAttribute");
+ return getMemorizedOrCurrentInputConnection()
+ .setComposingText(text, newCursorPosition, textAttribute);
+ }
+ case "setComposingRegion(int,int)": {
final int start = command.getExtras().getInt("start");
final int end = command.getExtras().getInt("end");
return getMemorizedOrCurrentInputConnection().setComposingRegion(start,
end);
}
+ case "setComposingRegion(int,int,TextAttribute)": {
+ final int start = command.getExtras().getInt("start");
+ final int end = command.getExtras().getInt("end");
+ final TextAttribute textAttribute =
+ command.getExtras().getParcelable("textAttribute");
+ return getMemorizedOrCurrentInputConnection()
+ .setComposingRegion(start, end, textAttribute);
+ }
case "finishComposingText":
return getMemorizedOrCurrentInputConnection().finishComposingText();
- case "commitText": {
+ case "commitText(CharSequence,int)": {
final CharSequence text = command.getExtras().getCharSequence("text");
final int newCursorPosition =
command.getExtras().getInt("newCursorPosition");
return getMemorizedOrCurrentInputConnection().commitText(text,
newCursorPosition);
}
+ case "commitText(CharSequence,int,TextAttribute)": {
+ final CharSequence text = command.getExtras().getCharSequence("text");
+ final int newCursorPosition =
+ command.getExtras().getInt("newCursorPosition");
+ final TextAttribute textAttribute =
+ command.getExtras().getParcelable("textAttribute");
+ return getMemorizedOrCurrentInputConnection()
+ .commitText(text, newCursorPosition, textAttribute);
+ }
case "commitCompletion": {
final CompletionInfo text = command.getExtras().getParcelable("text");
return getMemorizedOrCurrentInputConnection().commitCompletion(text);
@@ -298,6 +332,9 @@
case "performSpellCheck": {
return getMemorizedOrCurrentInputConnection().performSpellCheck();
}
+ case "takeSnapshot": {
+ return getMemorizedOrCurrentInputConnection().takeSnapshot();
+ }
case "performPrivateCommand": {
final String action = command.getExtras().getString("action");
final Bundle data = command.getExtras().getBundle("data");
@@ -322,6 +359,12 @@
return getMemorizedOrCurrentInputConnection().commitContent(
inputContentInfo, flags, opts);
}
+ case "setImeConsumesInput": {
+ final boolean imeConsumesInput =
+ command.getExtras().getBoolean("imeConsumesInput");
+ return getMemorizedOrCurrentInputConnection().setImeConsumesInput(
+ imeConsumesInput);
+ }
case "setBackDisposition": {
final int backDisposition =
command.getExtras().getInt("backDisposition");
@@ -424,6 +467,14 @@
return ImeEvent.RETURN_VALUE_UNAVAILABLE;
}
+ case "getStylusHandwritingWindowVisibility": {
+ View decorView = getStylusHandwritingWindow().getDecorView();
+ return decorView != null && decorView.getVisibility() == View.VISIBLE;
+ }
+ case "finishStylusHandwriting": {
+ finishStylusHandwriting();
+ return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+ }
case "getCurrentWindowMetricsBounds": {
return getSystemService(WindowManager.class)
.getCurrentWindowMetrics().getBounds();
@@ -541,7 +592,7 @@
final Handler handler = new Handler(mHandlerThread.getLooper());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
registerReceiver(mCommandReceiver, filter, null /* broadcastPermission */, handler,
- Context.RECEIVER_VISIBLE_TO_INSTANT_APPS);
+ Context.RECEIVER_VISIBLE_TO_INSTANT_APPS | Context.RECEIVER_EXPORTED);
} else {
registerReceiver(mCommandReceiver, filter, null /* broadcastPermission */, handler);
}
@@ -589,8 +640,20 @@
@Override
public boolean onEvaluateFullscreenMode() {
- return getTracer().onEvaluateFullscreenMode(() ->
- mSettings.fullscreenModeAllowed(false) && super.onEvaluateFullscreenMode());
+ return getTracer().onEvaluateFullscreenMode(() -> {
+ final int policy = mSettings.fullscreenModePolicy();
+ switch (policy) {
+ case ImeSettings.FullscreenModePolicy.NO_FULLSCREEN:
+ return false;
+ case ImeSettings.FullscreenModePolicy.FORCE_FULLSCREEN:
+ return true;
+ case ImeSettings.FullscreenModePolicy.OS_DEFAULT:
+ return super.onEvaluateFullscreenMode();
+ default:
+ Log.e(TAG, "unknown FullscreenModePolicy=" + policy);
+ return false;
+ }
+ });
}
private static final class KeyboardLayoutView extends LinearLayout {
@@ -794,6 +857,18 @@
}
@Override
+ public boolean onStartStylusHandwriting() {
+ getTracer().onStartStylusHandwriting(() -> super.onStartStylusHandwriting());
+ return true;
+ }
+
+ @Override
+ public void onFinishStylusHandwriting() {
+ getTracer().onFinishStylusHandwriting(() -> super.onFinishStylusHandwriting());
+ }
+
+
+ @Override
public void onFinishInputView(boolean finishingInput) {
getTracer().onFinishInputView(finishingInput,
() -> super.onFinishInputView(finishingInput));
@@ -917,8 +992,25 @@
stylesBuilder.addStyle(InlineSuggestionUi.newStyleBuilder().build());
Bundle styles = stylesBuilder.build();
+ final boolean supportedClientInlineSuggestions;
+ final boolean supportedServiceInlineSuggestions;
+ final boolean supportedInlineSuggestions;
if (mInlineSuggestionsExtras != null) {
styles.putAll(mInlineSuggestionsExtras);
+ supportedClientInlineSuggestions =
+ mInlineSuggestionsExtras.getBoolean("ClientSuggestions", true);
+ supportedServiceInlineSuggestions =
+ mInlineSuggestionsExtras.getBoolean("ServiceSuggestions", true);
+ supportedInlineSuggestions =
+ mInlineSuggestionsExtras.getBoolean("InlineSuggestions", true);
+ } else {
+ supportedClientInlineSuggestions = true;
+ supportedServiceInlineSuggestions = true;
+ supportedInlineSuggestions = true;
+ }
+
+ if (!supportedInlineSuggestions) {
+ return null;
}
return getTracer().onCreateInlineSuggestionsRequest(() -> {
@@ -934,6 +1026,8 @@
final InlineSuggestionsRequest.Builder builder =
new InlineSuggestionsRequest.Builder(presentationSpecs)
.setInlineTooltipPresentationSpec(tooltipSpec)
+ .setClientSupported(supportedClientInlineSuggestions)
+ .setServiceSupported(supportedServiceInlineSuggestions)
.setMaxSuggestionCount(6);
if (mInlineSuggestionsExtras != null) {
builder.setExtras(mInlineSuggestionsExtras.deepCopy());
@@ -1141,6 +1235,14 @@
recordEventInternal("onStartInputView", runnable, arguments);
}
+ void onStartStylusHandwriting(@NonNull Runnable runnable) {
+ recordEventInternal("onStartStylusHandwriting", runnable);
+ }
+
+ void onFinishStylusHandwriting(@NonNull Runnable runnable) {
+ recordEventInternal("onFinishStylusHandwriting", runnable);
+ }
+
void onFinishInputView(boolean finishingInput, @NonNull Runnable runnable) {
final Bundle arguments = new Bundle();
arguments.putBoolean("finishingInput", finishingInput);
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
index 2aa20c1..93c04747 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -30,6 +30,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
@@ -45,8 +46,11 @@
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.TextAttribute;
+import androidx.annotation.AnyThread;
import androidx.annotation.GuardedBy;
+import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -76,6 +80,9 @@
@NonNull
private final UiAutomation mUiAutomation;
+ @NonNull
+ private final AtomicBoolean mActive = new AtomicBoolean(true);
+
private final HandlerThread mHandlerThread = new HandlerThread("EventReceiver");
private static final class EventStore {
@@ -211,9 +218,15 @@
writeMockImeSettings(mContext, mImeEventActionName, imeSettings);
mHandlerThread.start();
- mContext.registerReceiver(mEventReceiver,
- new IntentFilter(mImeEventActionName), null /* broadcastPermission */,
- new Handler(mHandlerThread.getLooper()));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ mContext.registerReceiver(mEventReceiver,
+ new IntentFilter(mImeEventActionName), null /* broadcastPermission */,
+ new Handler(mHandlerThread.getLooper()), Context.RECEIVER_EXPORTED);
+ } else {
+ mContext.registerReceiver(mEventReceiver,
+ new IntentFilter(mImeEventActionName), null /* broadcastPermission */,
+ new Handler(mHandlerThread.getLooper()));
+ }
executeShellCommand(mUiAutomation, "ime enable " + getMockImeId());
executeShellCommand(mUiAutomation, "ime set " + getMockImeId());
@@ -292,10 +305,20 @@
}
/**
+ * @return {@code true} until {@link #close()} gets called.
+ */
+ @AnyThread
+ public boolean isActive() {
+ return mActive.get();
+ }
+
+ /**
* 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 {
+ mActive.set(false);
+
executeShellCommand(mUiAutomation, "ime reset");
PollingCheck.check("Make sure that MockIME becomes unavailable", TIMEOUT, () ->
@@ -440,6 +463,36 @@
}
/**
+ * Lets {@link MockIme} to call {@link InputConnection#getSurroundingText(int, int, int)} with
+ * the given parameters.
+ *
+ * <p>This triggers {@code getCurrentInputConnection().getSurroundingText(int, int, int)}.</p>
+ *
+ * <p>Use {@link ImeEvent#getReturnParcelableValue()} for {@link ImeEvent} returned from
+ * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
+ * value returned from the API.</p>
+ *
+ * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
+ *
+ * @param beforeLength The expected length of the text before the cursor.
+ * @param afterLength The expected length of the text after the cursor.
+ * @param flags Supplies additional options controlling how the text is returned. May be either
+ * {@code 0} or {@link InputConnection#GET_TEXT_WITH_STYLES}.
+ * @return {@link ImeCommand} object that can be passed to
+ * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+ * wait until this event is handled by {@link MockIme}.
+ */
+ @NonNull
+ public ImeCommand callGetSurroundingText(@IntRange(from = 0) int beforeLength,
+ @IntRange(from = 0) int afterLength, int flags) {
+ final Bundle params = new Bundle();
+ params.putInt("beforeLength", beforeLength);
+ params.putInt("afterLength", afterLength);
+ params.putInt("flags", flags);
+ return callCommandInternal("getSurroundingText", params);
+ }
+
+ /**
* Lets {@link MockIme} to call {@link InputConnection#getCursorCapsMode(int)} with the given
* parameters.
*
@@ -569,7 +622,39 @@
final Bundle params = new Bundle();
params.putCharSequence("text", text);
params.putInt("newCursorPosition", newCursorPosition);
- return callCommandInternal("setComposingText", params);
+ return callCommandInternal("setComposingText(CharSequence,int)", params);
+ }
+
+ /**
+ * Lets {@link MockIme} to call
+ * {@link InputConnection#setComposingText(CharSequence, int, TextAttribute)} with the given
+ * parameters.
+ *
+ * <p>This triggers
+ * {@code getCurrentInputConnection().setComposingText(text, newCursorPosition, textAttribute)}.
+ * </p>
+ *
+ * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
+ * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
+ * value returned from the API.</p>
+ *
+ * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
+ *
+ * @param text to be passed as the {@code text} parameter
+ * @param newCursorPosition to be passed as the {@code newCursorPosition} parameter
+ * @param textAttribute to be passed as the {@code textAttribute} parameter
+ * @return {@link ImeCommand} object that can be passed to
+ * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+ * wait until this event is handled by {@link MockIme}
+ */
+ @NonNull
+ public ImeCommand callSetComposingText(@Nullable CharSequence text, int newCursorPosition,
+ @Nullable TextAttribute textAttribute) {
+ final Bundle params = new Bundle();
+ params.putCharSequence("text", text);
+ params.putInt("newCursorPosition", newCursorPosition);
+ params.putParcelable("textAttribute", textAttribute);
+ return callCommandInternal("setComposingText(CharSequence,int,TextAttribute)", params);
}
/**
@@ -595,7 +680,37 @@
final Bundle params = new Bundle();
params.putInt("start", start);
params.putInt("end", end);
- return callCommandInternal("setComposingRegion", params);
+ return callCommandInternal("setComposingRegion(int,int)", params);
+ }
+
+ /**
+ * Lets {@link MockIme} to call
+ * {@link InputConnection#setComposingRegion(int, int, TextAttribute)} with the given
+ * parameters.
+ *
+ * <p>This triggers
+ * {@code getCurrentInputConnection().setComposingRegion(start, end, TextAttribute)}.</p>
+ *
+ * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
+ * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
+ * value returned from the API.</p>
+ *
+ * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
+ *
+ * @param start to be passed as the {@code start} parameter
+ * @param end to be passed as the {@code end} parameter
+ * @return {@link ImeCommand} object that can be passed to
+ * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+ * wait until this event is handled by {@link MockIme}.
+ */
+ @NonNull
+ public ImeCommand callSetComposingRegion(int start, int end,
+ @Nullable TextAttribute textAttribute) {
+ final Bundle params = new Bundle();
+ params.putInt("start", start);
+ params.putInt("end", end);
+ params.putParcelable("textAttribute", textAttribute);
+ return callCommandInternal("setComposingRegion(int,int,TextAttribute)", params);
}
/**
@@ -643,7 +758,37 @@
final Bundle params = new Bundle();
params.putCharSequence("text", text);
params.putInt("newCursorPosition", newCursorPosition);
- return callCommandInternal("commitText", params);
+ return callCommandInternal("commitText(CharSequence,int)", params);
+ }
+
+ /**
+ * Lets {@link MockIme} to call
+ * {@link InputConnection#commitText(CharSequence, int, TextAttribute)} with the given
+ * parameters.
+ *
+ * <p>This triggers
+ * {@code getCurrentInputConnection().commitText(text, newCursorPosition, TextAttribute)}.</p>
+ *
+ * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
+ * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
+ * value returned from the API.</p>
+ *
+ * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
+ *
+ * @param text to be passed as the {@code text} parameter
+ * @param newCursorPosition to be passed as the {@code newCursorPosition} parameter
+ * @return {@link ImeCommand} object that can be passed to
+ * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+ * wait until this event is handled by {@link MockIme}
+ */
+ @NonNull
+ public ImeCommand callCommitText(@Nullable CharSequence text, int newCursorPosition,
+ @Nullable TextAttribute textAttribute) {
+ final Bundle params = new Bundle();
+ params.putCharSequence("text", text);
+ params.putInt("newCursorPosition", newCursorPosition);
+ params.putParcelable("textAttribute", textAttribute);
+ return callCommandInternal("commitText(CharSequence,int,TextAttribute)", params);
}
/**
@@ -853,6 +998,22 @@
}
/**
+ * Lets {@link MockIme} to call {@link InputConnection#takeSnapshot()}.
+ *
+ * <p>This triggers {@code getCurrentInputConnection().takeSnapshot()}.</p>
+ *
+ * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
+
+ * @return {@link ImeCommand} object that can be passed to
+ * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+ * wait until this event is handled by {@link MockIme}
+ */
+ @NonNull
+ public ImeCommand callTakeSnapshot() {
+ return callCommandInternal("takeSnapshot", new Bundle());
+ }
+
+ /**
* Lets {@link MockIme} to call {@link InputConnection#clearMetaKeyStates(int)} with the given
* parameters.
*
@@ -1024,6 +1185,30 @@
}
/**
+ * Lets {@link MockIme} to call {@link InputConnection#setImeConsumesInput(boolean)} with the
+ * given parameters.
+ *
+ * <p>This triggers {@code getCurrentInputConnection().setImeConsumesInput(boolean)}.</p>
+ *
+ * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
+ * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
+ * value returned from the API.</p>
+ *
+ * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
+ *
+ * @param imeConsumesInput to be passed as the {@code imeConsumesInput} parameter
+ * @return {@link ImeCommand} object that can be passed to
+ * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+ * wait until this event is handled by {@link MockIme}
+ */
+ @NonNull
+ public ImeCommand callSetImeConsumesInput(boolean imeConsumesInput) {
+ final Bundle params = new Bundle();
+ params.putBoolean("imeConsumesInput", imeConsumesInput);
+ return callCommandInternal("setImeConsumesInput", params);
+ }
+
+ /**
* Lets {@link MockIme} to call
* {@link android.inputmethodservice.InputMethodService#setBackDisposition(int)} with the given
* parameters.
@@ -1191,6 +1376,16 @@
}
@NonNull
+ public ImeCommand callGetStylusHandwritingWindowVisibility() {
+ return callCommandInternal("getStylusHandwritingWindowVisibility", new Bundle());
+ }
+
+ @NonNull
+ public ImeCommand callFinishStylusHandwriting() {
+ return callCommandInternal("finishStylusHandwriting", new Bundle());
+ }
+
+ @NonNull
public ImeCommand callGetCurrentWindowMetricsBounds() {
return callCommandInternal("getCurrentWindowMetricsBounds", new Bundle());
}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
index f3bd570..8833f41 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
@@ -16,6 +16,8 @@
package android.view.inputmethod.cts;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -40,8 +42,10 @@
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.SurroundingText;
+import android.view.inputmethod.TextSnapshot;
import android.view.inputmethod.cts.util.InputConnectionTestUtils;
+import androidx.annotation.NonNull;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -53,6 +57,9 @@
@RunWith(AndroidJUnit4.class)
public class BaseInputConnectionTest {
+ private static final int CAPS_MODE_MASK = TextUtils.CAP_MODE_CHARACTERS
+ | TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES;
+
private static BaseInputConnection createBaseInputConnection() {
final View view = new View(InstrumentationRegistry.getInstrumentation().getTargetContext());
return new BaseInputConnection(view, true);
@@ -189,6 +196,23 @@
}
/**
+ * An utility method to create an instance of {@link BaseInputConnection} from {@link Editable}.
+ *
+ * @param editable the initial text.
+ * @return {@link BaseInputConnection} instantiated in the full editor mode with
+ * {@code editable}.
+ */
+ private static BaseInputConnection createConnection(@NonNull Editable editable) {
+ final View view = new View(InstrumentationRegistry.getInstrumentation().getTargetContext());
+ return new BaseInputConnection(view, true) {
+ @Override
+ public Editable getEditable() {
+ return editable;
+ }
+ };
+ }
+
+ /**
* An utility method to create an instance of {@link BaseInputConnection} in the full editor
* mode with an initial text and selection range.
*
@@ -201,13 +225,7 @@
final int selectionEnd = Selection.getSelectionEnd(source);
final Editable editable = Editable.Factory.getInstance().newEditable(source);
Selection.setSelection(editable, selectionStart, selectionEnd);
- final View view = new View(InstrumentationRegistry.getInstrumentation().getTargetContext());
- return new BaseInputConnection(view, true) {
- @Override
- public Editable getEditable() {
- return editable;
- }
- };
+ return createConnection(editable);
}
private static void verifyDeleteSurroundingTextMain(final String initialState,
@@ -616,4 +634,44 @@
100, BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
expectThrows(IllegalArgumentException.class, ()-> connection.getTextAfterCursor(-1, 0));
}
+
+ @Test
+ public void testTakeSnapshot() {
+ final BaseInputConnection connection = createConnectionWithSelection(
+ InputConnectionTestUtils.formatString("0123[456]789"));
+
+ verifyTextSnapshot(connection);
+
+ connection.setSelection(10, 10);
+ verifyTextSnapshot(connection);
+
+ connection.setComposingRegion(3, 10);
+ verifyTextSnapshot(connection);
+
+ connection.finishComposingText();
+ verifyTextSnapshot(connection);
+ }
+
+ @Test
+ public void testTakeSnapshotForNoSelection() {
+ final BaseInputConnection connection = createConnection(
+ Editable.Factory.getInstance().newEditable("test"));
+ // null should be returned for text with no selection.
+ assertThat(connection.takeSnapshot()).isNull();
+ }
+
+ private void verifyTextSnapshot(@NonNull BaseInputConnection connection) {
+ final Editable editable = connection.getEditable();
+
+ final TextSnapshot snapshot = connection.takeSnapshot();
+ assertThat(snapshot).isNotNull();
+ assertThat(snapshot.getSelectionStart()).isEqualTo(Selection.getSelectionStart(editable));
+ assertThat(snapshot.getSelectionEnd()).isEqualTo(Selection.getSelectionEnd(editable));
+ assertThat(snapshot.getCompositionStart())
+ .isEqualTo(BaseInputConnection.getComposingSpanStart(editable));
+ assertThat(snapshot.getCompositionEnd())
+ .isEqualTo(BaseInputConnection.getComposingSpanEnd(editable));
+ assertThat(snapshot.getCursorCapsMode()).isEqualTo(
+ connection.getCursorCapsMode(CAPS_MODE_MASK));
+ }
}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/CursorAnchorInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/CursorAnchorInfoTest.java
index 466a7cd..d8128a8 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/CursorAnchorInfoTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/CursorAnchorInfoTest.java
@@ -32,6 +32,7 @@
import android.text.TextUtils;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.CursorAnchorInfo.Builder;
+import android.view.inputmethod.EditorBoundsInfo;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -103,12 +104,17 @@
Matrix transformMatrix = new Matrix();
transformMatrix.setScale(10.0f, 20.0f);
+ final EditorBoundsInfo boundsInfo =
+ new EditorBoundsInfo.Builder().setEditorBounds(MANY_BOUNDS[0])
+ .setHandwritingBounds(MANY_BOUNDS[1]).build();
+
final Builder builder = new Builder();
builder.setSelectionRange(selectionStart, selectionEnd)
.setComposingText(composingTextStart, composingText)
.setInsertionMarkerLocation(insertionMarkerHorizontal, insertionMarkerTop,
insertionMarkerBaseline, insertionMarkerBottom, insertionMarkerFlags)
- .setMatrix(transformMatrix);
+ .setMatrix(transformMatrix)
+ .setEditorBoundsInfo(boundsInfo);
for (int i = 0; i < MANY_BOUNDS.length; i++) {
final RectF bounds = MANY_BOUNDS[i];
final int flags = MANY_FLAGS_ARRAY[i];
@@ -127,6 +133,11 @@
assertEquals(insertionMarkerBaseline, info.getInsertionMarkerBaseline(), EPSILON);
assertEquals(insertionMarkerBottom, info.getInsertionMarkerBottom(), EPSILON);
assertEquals(transformMatrix, info.getMatrix());
+ assertEquals(boundsInfo, info.getEditorBoundsInfo());
+ assertEquals(MANY_BOUNDS[0],
+ info.getEditorBoundsInfo().getEditorBounds());
+ assertEquals(MANY_BOUNDS[1],
+ info.getEditorBoundsInfo().getHandwritingBounds());
for (int i = 0; i < MANY_BOUNDS.length; i++) {
final RectF expectedBounds = MANY_BOUNDS[i];
assertEquals(expectedBounds, info.getCharacterBounds(i));
@@ -151,6 +162,7 @@
assertEquals(insertionMarkerTop, info2.getInsertionMarkerTop(), EPSILON);
assertEquals(insertionMarkerBaseline, info2.getInsertionMarkerBaseline(), EPSILON);
assertEquals(insertionMarkerBottom, info2.getInsertionMarkerBottom(), EPSILON);
+ assertEquals(boundsInfo, info2.getEditorBoundsInfo());
assertEquals(transformMatrix, info2.getMatrix());
for (int i = 0; i < MANY_BOUNDS.length; i++) {
final RectF expectedBounds = MANY_BOUNDS[i];
@@ -178,6 +190,7 @@
assertEquals(insertionMarkerTop, info3.getInsertionMarkerTop(), EPSILON);
assertEquals(insertionMarkerBaseline, info3.getInsertionMarkerBaseline(), EPSILON);
assertEquals(insertionMarkerBottom, info3.getInsertionMarkerBottom(), EPSILON);
+ assertEquals(boundsInfo, info3.getEditorBoundsInfo());
assertEquals(transformMatrix, info3.getMatrix());
for (int i = 0; i < MANY_BOUNDS.length; i++) {
final RectF expectedBounds = MANY_BOUNDS[i];
@@ -204,6 +217,7 @@
assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerTop(), EPSILON);
assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerBaseline(), EPSILON);
assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerBottom(), EPSILON);
+ assertEquals(null, uninitializedInfo.getEditorBoundsInfo());
assertEquals(new Matrix(), uninitializedInfo.getMatrix());
}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/EditTextImeSupportTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/EditTextImeSupportTest.java
index b1e0c1d..66c27f3 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/EditTextImeSupportTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/EditTextImeSupportTest.java
@@ -30,7 +30,10 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.graphics.Color;
import android.os.SystemClock;
+import android.view.View;
+import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.SurroundingText;
@@ -198,6 +201,109 @@
}
/**
+ * Test when to see {@link EditorInfo#IME_FLAG_NAVIGATE_NEXT} and
+ * {@link EditorInfo#IME_FLAG_NAVIGATE_PREVIOUS}.
+ *
+ * <p>This is also a regression test for Bug 31099943.</p>
+ */
+ @Test
+ public void testNavigateFlags() throws Exception {
+ try (MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder())) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ // For a single EditText, there should be no navigate flag
+ verifyNavigateFlags(stream, new Control[]{
+ Control.FOCUSED_EDIT_TEXT,
+ }, false /* navigateNext */, false /* navigatePrevious */);
+
+ // For two EditText controls, there should be one navigate flag depending on the
+ // geometry.
+ verifyNavigateFlags(stream, new Control[]{
+ Control.FOCUSED_EDIT_TEXT,
+ Control.EDIT_TEXT,
+ }, true /* navigateNext */, false /* navigatePrevious */);
+ verifyNavigateFlags(stream, new Control[]{
+ Control.EDIT_TEXT,
+ Control.FOCUSED_EDIT_TEXT,
+ }, false /* navigateNext */, true /* navigatePrevious */);
+
+ // Non focusable View controls should be ignored when determining navigation flags.
+ verifyNavigateFlags(stream, new Control[]{
+ Control.NON_FOCUSABLE_VIEW,
+ Control.FOCUSED_EDIT_TEXT,
+ Control.NON_FOCUSABLE_VIEW,
+ }, false /* navigateNext */, false /* navigatePrevious */);
+
+ // Even focusable View controls should be ignored when determining navigation flags if
+ // View#onCheckIsTextEditor() returns false. (Regression test for Bug 31099943)
+ verifyNavigateFlags(stream, new Control[]{
+ Control.FOCUSABLE_VIEW,
+ Control.FOCUSED_EDIT_TEXT,
+ Control.FOCUSABLE_VIEW,
+ }, false /* navigateNext */, false /* navigatePrevious */);
+ }
+ }
+
+ private enum Control {
+ EDIT_TEXT,
+ FOCUSED_EDIT_TEXT,
+ FOCUSABLE_VIEW,
+ NON_FOCUSABLE_VIEW,
+ }
+
+ private void verifyNavigateFlags(@NonNull ImeEventStream stream, @NonNull Control[] controls,
+ boolean navigateNext, boolean navigatePrevious) throws Exception {
+ final String marker = getTestMarker();
+ TestActivity.startSync(activity-> {
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ for (Control control : controls) {
+ switch (control) {
+ case EDIT_TEXT:
+ case FOCUSED_EDIT_TEXT: {
+ final boolean focused = (Control.FOCUSED_EDIT_TEXT == control);
+ final EditText editText = new EditText(activity);
+ editText.setHint("editText");
+ layout.addView(editText);
+ if (focused) {
+ editText.setPrivateImeOptions(marker);
+ editText.requestFocus();
+ }
+ break;
+ }
+ case FOCUSABLE_VIEW:
+ case NON_FOCUSABLE_VIEW: {
+ final boolean focusable = (Control.FOCUSABLE_VIEW == control);
+ final View view = new View(activity);
+ view.setBackgroundColor(focusable ? Color.YELLOW : Color.RED);
+ view.setFocusable(focusable);
+ view.setFocusableInTouchMode(focusable);
+ view.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, 10 /* height */));
+ layout.addView(view);
+ break;
+ }
+ default:
+ throw new UnsupportedOperationException("Unknown control=" + control);
+ }
+ }
+ return layout;
+ });
+
+ final ImeEvent startInput = expectEvent(stream,
+ editorMatcher("onStartInput", marker), TIMEOUT);
+ final EditorInfo editorInfo = startInput.getArguments().getParcelable("editorInfo");
+ assertThat(editorInfo).isNotNull();
+ assertThat(editorInfo.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT)
+ .isEqualTo(navigateNext ? EditorInfo.IME_FLAG_NAVIGATE_NEXT : 0);
+ assertThat(editorInfo.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS)
+ .isEqualTo(navigatePrevious ? EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS : 0);
+ }
+
+ /**
* Regression test for Bug 209958658.
*/
@Test
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
index 34d4443..766113f 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
@@ -17,7 +17,11 @@
package android.view.inputmethod.cts;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE;
import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible;
import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeVisible;
import static android.view.inputmethod.cts.util.TestUtils.runOnMainSync;
@@ -32,6 +36,7 @@
import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -54,6 +59,7 @@
import android.view.inputmethod.cts.util.AutoCloseableWrapper;
import android.view.inputmethod.cts.util.EndToEndImeTestBase;
import android.view.inputmethod.cts.util.TestActivity;
+import android.view.inputmethod.cts.util.TestActivity2;
import android.view.inputmethod.cts.util.TestUtils;
import android.view.inputmethod.cts.util.UnlockScreenRule;
import android.view.inputmethod.cts.util.WindowFocusHandleService;
@@ -76,6 +82,7 @@
import com.android.cts.mockime.ImeSettings;
import com.android.cts.mockime.MockImeSession;
+import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -136,10 +143,7 @@
@FlakyTest(bugId = 149246840)
@Test
public void testOnStartInputCalledOnceIme() throws Exception {
- try (MockImeSession imeSession = MockImeSession.create(
- InstrumentationRegistry.getInstrumentation().getContext(),
- InstrumentationRegistry.getInstrumentation().getUiAutomation(),
- new ImeSettings.Builder())) {
+ try (MockImeSession imeSession = createTestImeSession()) {
final ImeEventStream stream = imeSession.openEventStream();
final String marker = getTestMarker();
@@ -167,10 +171,7 @@
@Test
public void testSoftInputStateAlwaysVisibleWithoutFocusedEditorView() throws Exception {
- try (MockImeSession imeSession = MockImeSession.create(
- InstrumentationRegistry.getInstrumentation().getContext(),
- InstrumentationRegistry.getInstrumentation().getUiAutomation(),
- new ImeSettings.Builder())) {
+ try (MockImeSession imeSession = createTestImeSession()) {
final ImeEventStream stream = imeSession.openEventStream();
final String marker = getTestMarker();
@@ -209,11 +210,69 @@
}
@Test
+ public void testNoEditorNoStartInput() throws Exception {
+ Assume.assumeTrue(isPreventImeStartup());
+ try (MockImeSession imeSession = createTestImeSession()) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ final String marker = getTestMarker();
+ TestActivity.startSync(activity -> {
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ final TextView textView = new TextView(activity) {
+ @Override
+ public boolean onCheckIsTextEditor() {
+ return false;
+ }
+ };
+ textView.setText("textView");
+ textView.requestFocus();
+ textView.setPrivateImeOptions(marker);
+ layout.addView(textView);
+ return layout;
+ });
+
+ // Input shouldn't start
+ notExpectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+ }
+ }
+
+ @Test
+ public void testDelayedAddEditorStartsInput() throws Exception {
+ Assume.assumeTrue(isPreventImeStartup());
+ try (MockImeSession imeSession = createTestImeSession()) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ final AtomicReference<LinearLayout> layoutRef = new AtomicReference<>();
+ final TestActivity testActivity = TestActivity.startSync(activity -> {
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ layoutRef.set(layout);
+
+ return layout;
+ });
+
+ // Activity adds EditText at a later point.
+ TestUtils.waitOnMainUntil(() -> layoutRef.get().hasWindowFocus(), TIMEOUT);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ final String marker = getTestMarker();
+ testActivity.runOnUiThread(() -> {
+ final EditText editText = new EditText(testActivity);
+ editText.setText("Editable");
+ editText.setPrivateImeOptions(marker);
+ layoutRef.get().addView(editText);
+ editText.requestFocus();
+ });
+
+ // Input should start
+ expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+ }
+ }
+
+ @Test
public void testEditorStartsInput() throws Exception {
- try (MockImeSession imeSession = MockImeSession.create(
- InstrumentationRegistry.getInstrumentation().getContext(),
- InstrumentationRegistry.getInstrumentation().getUiAutomation(),
- new ImeSettings.Builder())) {
+ try (MockImeSession imeSession = createTestImeSession()) {
final ImeEventStream stream = imeSession.openEventStream();
final String marker = getTestMarker();
@@ -236,10 +295,7 @@
@Test
public void testSoftInputStateAlwaysVisibleFocusedEditorView() throws Exception {
- try (MockImeSession imeSession = MockImeSession.create(
- InstrumentationRegistry.getInstrumentation().getContext(),
- InstrumentationRegistry.getInstrumentation().getUiAutomation(),
- new ImeSettings.Builder())) {
+ try (MockImeSession imeSession = createTestImeSession()) {
final ImeEventStream stream = imeSession.openEventStream();
TestActivity.startSync(activity -> {
@@ -277,15 +333,12 @@
@Test
public void testFocusableWindowDoesNotInvalidateExistingInputConnection() throws Exception {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- try (MockImeSession imeSession = MockImeSession.create(
- instrumentation.getContext(),
- instrumentation.getUiAutomation(),
- new ImeSettings.Builder())) {
+ try (MockImeSession imeSession = createTestImeSession()) {
final ImeEventStream stream = imeSession.openEventStream();
final String marker = getTestMarker();
final EditText editText = launchTestActivity(marker);
- instrumentation.runOnMainSync(() -> editText.requestFocus());
+ instrumentation.runOnMainSync(editText::requestFocus);
// Wait until the MockIme gets bound to the TestActivity.
expectBindInput(stream, Process.myPid(), TIMEOUT);
@@ -359,7 +412,7 @@
instrumentation.waitForIdleSync();
// Make sure that the EditText now has window-focus again.
- TestUtils.waitOnMainUntil(() -> editText.hasWindowFocus(), TIMEOUT);
+ TestUtils.waitOnMainUntil(editText::hasWindowFocus, TIMEOUT);
// Make sure that InputConnection#commitText() works.
final ImeCommand commit4 = imeSession.callCommitText("Done!", 1);
@@ -379,10 +432,7 @@
@Test
public void testNonFocusablePopupWindowDoesNotAffectImeVisibility() throws Exception {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- try (MockImeSession imeSession = MockImeSession.create(
- instrumentation.getContext(),
- instrumentation.getUiAutomation(),
- new ImeSettings.Builder())) {
+ try (MockImeSession imeSession = createTestImeSession()) {
final ImeEventStream stream = imeSession.openEventStream();
final String marker = getTestMarker();
@@ -440,15 +490,12 @@
@Test
public void testRestartInputWhileOtherProcessHasWindowFocus() throws Exception {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- try (MockImeSession imeSession = MockImeSession.create(
- instrumentation.getContext(),
- instrumentation.getUiAutomation(),
- new ImeSettings.Builder())) {
+ try (MockImeSession imeSession = createTestImeSession()) {
final ImeEventStream stream = imeSession.openEventStream();
final String marker = getTestMarker();
final EditText editText = launchTestActivity(marker);
- instrumentation.runOnMainSync(() -> editText.requestFocus());
+ instrumentation.runOnMainSync(editText::requestFocus);
// Wait until the MockIme gets bound to the TestActivity.
expectBindInput(stream, Process.myPid(), TIMEOUT);
@@ -457,7 +504,7 @@
// Get app window token
final IBinder appWindowToken = TestUtils.getOnMainSync(
- () -> editText.getApplicationWindowToken());
+ editText::getApplicationWindowToken);
try (WindowFocusStealer focusStealer =
WindowFocusStealer.connect(instrumentation.getTargetContext(), TIMEOUT)) {
@@ -476,7 +523,7 @@
}
// Wait until the edit text gains window focus again.
- TestUtils.waitOnMainUntil(() -> editText.hasWindowFocus(), TIMEOUT);
+ TestUtils.waitOnMainUntil(editText::hasWindowFocus, TIMEOUT);
// Make sure that InputConnection#commitText() still works.
final ImeCommand command = imeSession.callCommitText("test commit", 1);
@@ -492,10 +539,7 @@
*/
@Test
public void testSetShowInputOnFocus() throws Exception {
- try (MockImeSession imeSession = MockImeSession.create(
- InstrumentationRegistry.getInstrumentation().getContext(),
- InstrumentationRegistry.getInstrumentation().getUiAutomation(),
- new ImeSettings.Builder())) {
+ try (MockImeSession imeSession = createTestImeSession()) {
final ImeEventStream stream = imeSession.openEventStream();
final String marker = getTestMarker();
@@ -520,9 +564,7 @@
public void testMultiWindowFocusHandleOnDifferentUiThread() throws Exception {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
try (CloseOnce session = CloseOnce.of(new ServiceSession(instrumentation.getContext()));
- MockImeSession imeSession = MockImeSession.create(
- instrumentation.getContext(), instrumentation.getUiAutomation(),
- new ImeSettings.Builder())) {
+ MockImeSession imeSession = createTestImeSession()) {
final ImeEventStream stream = imeSession.openEventStream();
final AtomicBoolean popupTextHasWindowFocus = new AtomicBoolean(false);
final AtomicBoolean popupTextHasViewFocus = new AtomicBoolean(false);
@@ -537,7 +579,7 @@
// Emulate tap event
CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText);
- TestUtils.waitOnMainUntil(() -> editTextHasWindowFocus.get(), TIMEOUT);
+ TestUtils.waitOnMainUntil(editTextHasWindowFocus::get, TIMEOUT);
expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
@@ -546,8 +588,8 @@
final ServiceSession serviceSession = (ServiceSession) session.mAutoCloseable;
final EditText popupTextView = serviceSession.getService().getPopupTextView(
popupTextHasWindowFocus);
- assertTrue(popupTextView.getHandler().getLooper()
- != serviceSession.getService().getMainLooper());
+ assertNotSame(popupTextView.getHandler().getLooper(),
+ serviceSession.getService().getMainLooper());
// Verify popupTextView will also receive window focus change and soft keyboard shown
// after tapping the view.
@@ -556,7 +598,7 @@
popupTextView.setPrivateImeOptions(marker1);
popupTextHasViewFocus.set(popupTextView.requestFocus());
});
- TestUtils.waitOnMainUntil(() -> popupTextHasViewFocus.get(), TIMEOUT);
+ TestUtils.waitOnMainUntil(popupTextHasViewFocus::get, TIMEOUT);
CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, popupTextView);
TestUtils.waitOnMainUntil(() -> popupTextHasWindowFocus.get()
@@ -575,7 +617,7 @@
// Remove the popTextView window and back to test activity, and then verify if
// commitText is still workable.
session.close();
- TestUtils.waitOnMainUntil(() -> editText.hasWindowFocus(), TIMEOUT);
+ TestUtils.waitOnMainUntil(editText::hasWindowFocus, TIMEOUT);
final ImeCommand commit = imeSession.callCommitText("test commit", 1);
expectCommand(stream, commit, TIMEOUT);
TestUtils.waitOnMainUntil(
@@ -586,9 +628,7 @@
@Test
public void testKeyboardStateAfterImeFocusableFlagChanged() throws Exception {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- try (MockImeSession imeSession = MockImeSession.create(
- instrumentation.getContext(), instrumentation.getUiAutomation(),
- new ImeSettings.Builder())) {
+ try (MockImeSession imeSession = createTestImeSession()) {
final ImeEventStream stream = imeSession.openEventStream();
final AtomicReference<EditText> editTextRef = new AtomicReference<>();
final String marker = getTestMarker();
@@ -666,10 +706,7 @@
@Test
public void testRequestFocusOnWindowFocusChanged() throws Exception {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- try (MockImeSession imeSession = MockImeSession.create(
- instrumentation.getContext(),
- instrumentation.getUiAutomation(),
- new ImeSettings.Builder())) {
+ try (MockImeSession imeSession = createTestImeSession()) {
final ImeEventStream stream = imeSession.openEventStream();
final String marker = getTestMarker();
final AtomicReference<EditText> editTextRef = new AtomicReference<>();
@@ -711,6 +748,132 @@
}
}
+ /**
+ * Start an activity with a focused test editor and wait for the IME to become visible,
+ * then start another activity with the given {@code softInputMode} and an <b>unfocused</b>
+ * test editor.
+ *
+ * @return the event stream positioned before the second app is launched
+ */
+ private ImeEventStream startFocusedEditorActivity_thenAnotherUnfocusedEditorActivity(
+ int softInputMode)
+ throws Exception {
+ try (MockImeSession imeSession = createTestImeSession()) {
+ final String marker = getTestMarker();
+
+ // Launch an activity with a text edit and request focus
+ TestActivity.startSync(activity -> {
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ final EditText editText = new EditText(activity);
+ editText.setText("editText");
+ editText.setPrivateImeOptions(marker);
+ editText.requestFocus();
+
+ activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+ layout.addView(editText);
+ return layout;
+ });
+
+ ImeEventStream stream = imeSession.openEventStream();
+
+ // Wait until the MockIme gets bound and started for the TestActivity.
+ expectBindInput(stream, Process.myPid(), TIMEOUT);
+ expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+ expectImeVisible(TIMEOUT);
+
+ // Skip events relating to showStateInitializeActivity() and TestActivity1
+ stream.skipAll();
+
+ // Launch another activity without a text edit but with the requested softInputMode set
+ TestActivity2.startSync(activity -> {
+ activity.getWindow().setSoftInputMode(softInputMode);
+
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ final EditText editText = new EditText(activity);
+ // Do not request focus for the editText
+ editText.setText("Unfocused editText");
+ layout.addView(editText);
+ return layout;
+ });
+
+ return stream;
+ }
+ }
+
+ @Test
+ public void testUnfocusedEditor_stateUnspecified_hidesIme() throws Exception {
+ ImeEventStream stream = startFocusedEditorActivity_thenAnotherUnfocusedEditorActivity(
+ SOFT_INPUT_STATE_UNSPECIFIED);
+ expectImeHidden(stream);
+ expectOnFinishInput(stream);
+ }
+
+ @Test
+ public void testUnfocusedEditor_stateHidden_hidesIme() throws Exception {
+ ImeEventStream stream = startFocusedEditorActivity_thenAnotherUnfocusedEditorActivity(
+ SOFT_INPUT_STATE_HIDDEN);
+ expectImeHidden(stream);
+ expectOnFinishInput(stream);
+ }
+
+ @Test
+ public void testUnfocusedEditor_stateAlwaysHidden_hidesIme() throws Exception {
+ ImeEventStream stream = startFocusedEditorActivity_thenAnotherUnfocusedEditorActivity(
+ SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+ expectImeHidden(stream);
+ expectOnFinishInput(stream);
+ }
+
+ @Test
+ public void testUnfocusedEditor_stateVisible_startsIme() throws Exception {
+ ImeEventStream stream = startFocusedEditorActivity_thenAnotherUnfocusedEditorActivity(
+ SOFT_INPUT_STATE_VISIBLE);
+ // The previous IME should be finished
+ expectOnFinishInput(stream);
+
+ // Input should be started and shown
+ expectEvent(stream, event -> "onStartInput".equals(event.getEventName()),
+ EXPECT_TIMEOUT);
+ expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
+ EXPECT_TIMEOUT);
+ }
+
+ @Test
+ public void testUnfocusedEditor_stateAlwaysVisible_startsIme() throws Exception {
+ ImeEventStream stream = startFocusedEditorActivity_thenAnotherUnfocusedEditorActivity(
+ SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+ // The previous IME should be finished
+ expectOnFinishInput(stream);
+
+ // Input should be started and shown
+ expectEvent(stream, event -> "onStartInput".equals(event.getEventName()),
+ EXPECT_TIMEOUT);
+ expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
+ EXPECT_TIMEOUT);
+ }
+
+ private static void expectImeHidden(@NonNull ImeEventStream stream) throws TimeoutException {
+ expectEvent(stream, event -> "hideSoftInput".equals(event.getEventName()), EXPECT_TIMEOUT);
+ }
+
+ private static void expectOnFinishInput(@NonNull ImeEventStream stream)
+ throws TimeoutException {
+ expectEvent(stream, event -> "onFinishInput".equals(event.getEventName()), EXPECT_TIMEOUT);
+ }
+
+ @NonNull
+ private static MockImeSession createTestImeSession() throws Exception {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ return MockImeSession.create(
+ instrumentation.getContext(),
+ instrumentation.getUiAutomation(),
+ new ImeSettings.Builder());
+ }
+
private static class ServiceSession implements ServiceConnection, AutoCloseable {
private final Context mContext;
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
index 1eac684..34de0c0 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
@@ -22,8 +22,6 @@
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible;
import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeVisible;
@@ -56,6 +54,7 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -122,26 +121,19 @@
() -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
expectImeVisible(TIMEOUT);
- final View[] childViewRoot = new View[1];
- TestUtils.runOnMainSync(() -> {
- final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
- attrs.token = activity.getWindow().getAttributes().token;
- attrs.type = TYPE_APPLICATION;
- attrs.width = 200;
- attrs.height = 200;
- attrs.format = PixelFormat.TRANSPARENT;
- attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM;
- attrs.setFitInsetsTypes(WindowInsets.Type.ime() | WindowInsets.Type.statusBars()
- | WindowInsets.Type.navigationBars());
- childViewRoot[0] = addChildWindow(activity, attrs);
- childViewRoot[0].setVisibility(View.VISIBLE);
- });
- TestUtils.waitOnMainUntil(() -> childViewRoot[0] != null
- && childViewRoot[0].getVisibility() == View.VISIBLE, TIMEOUT);
-
- PollingCheck.check("Ime insets should be visible", TIMEOUT,
- () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
- expectImeVisible(TIMEOUT);
+ try (ChildWindowHolder childWindow = createChildTransparentApplicationWindowOnMain(
+ activity, 200 /* width */, 200 /* height */,
+ FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM,
+ WindowInsets.Type.ime() | WindowInsets.Type.statusBars()
+ | WindowInsets.Type.navigationBars())) {
+ // The window will be shown above (in y-axis) the IME.
+ TestUtils.runOnMainSync(
+ () -> childWindow.getRootView().setVisibility(View.VISIBLE));
+ TestUtils.waitOnMainUntil(
+ () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()),
+ TIMEOUT, "Ime insets should be visible");
+ expectImeVisible(TIMEOUT);
+ }
}
}
@@ -171,26 +163,22 @@
() -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
expectImeVisible(TIMEOUT);
- final View[] childViewRoot = new View[1];
- TestUtils.runOnMainSync(() -> {
- final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
- attrs.type = TYPE_APPLICATION_PANEL;
- attrs.width = MATCH_PARENT;
- attrs.height = NEW_KEYBOARD_HEIGHT;
- attrs.gravity = Gravity.BOTTOM;
- attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM;
- childViewRoot[0] = addChildWindow(activity, attrs);
- childViewRoot[0].setBackgroundColor(Color.RED);
- childViewRoot[0].setVisibility(View.VISIBLE);
- });
- // The window will be shown above (in y-axis) the IME.
- TestUtils.waitOnMainUntil(() -> childViewRoot[0] != null
- && childViewRoot[0].getVisibility() == View.VISIBLE, TIMEOUT);
- // IME should be on screen without reset.
- notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
- PollingCheck.check("Ime insets should be visible", TIMEOUT,
- () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
- expectImeVisible(TIMEOUT);
+ try (ChildWindowHolder childWindow = createChildBottomPanelWindowOnMain(activity,
+ MATCH_PARENT /* width */, NEW_KEYBOARD_HEIGHT /* height */,
+ FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)) {
+ // The window will be shown above (in y-axis) the IME.
+ TestUtils.runOnMainSync(() -> {
+ childWindow.getRootView().setBackgroundColor(Color.RED);
+ childWindow.getRootView().setVisibility(View.VISIBLE);
+ });
+ // IME should be on screen without reset.
+ notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+
+ TestUtils.waitOnMainUntil(
+ () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()),
+ TIMEOUT, "Ime insets should be visible");
+ expectImeVisible(TIMEOUT);
+ }
}
}
@@ -220,26 +208,22 @@
() -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
expectImeVisible(TIMEOUT);
- final View[] childViewRoot = new View[1];
- TestUtils.runOnMainSync(() -> {
- final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
- attrs.type = TYPE_APPLICATION_PANEL;
- attrs.width = MATCH_PARENT;
- attrs.height = NEW_KEYBOARD_HEIGHT;
- attrs.gravity = Gravity.BOTTOM;
- attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM | FLAG_LAYOUT_IN_SCREEN;
- childViewRoot[0] = addChildWindow(activity, attrs);
- childViewRoot[0].setBackgroundColor(Color.RED);
- childViewRoot[0].setVisibility(View.VISIBLE);
- });
- // The window will be shown behind (in z-axis) the IME.
- TestUtils.waitOnMainUntil(() -> childViewRoot[0] != null
- && childViewRoot[0].getVisibility() == View.VISIBLE, TIMEOUT);
- // IME should be on screen without reset.
- notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
- PollingCheck.check("Ime insets should be visible", TIMEOUT,
- () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
- expectImeVisible(TIMEOUT);
+ try (ChildWindowHolder childWindow = createChildBottomPanelWindowOnMain(activity,
+ MATCH_PARENT /* width */, NEW_KEYBOARD_HEIGHT /* height */,
+ FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM | FLAG_LAYOUT_IN_SCREEN)) {
+ // The window will be shown behind (in z-axis) the IME.
+ TestUtils.runOnMainSync(() -> {
+ childWindow.getRootView().setBackgroundColor(Color.RED);
+ childWindow.getRootView().setVisibility(View.VISIBLE);
+ });
+ // IME should be on screen without reset.
+ notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+
+ TestUtils.waitOnMainUntil(
+ () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()),
+ TIMEOUT, "Ime insets should be visible");
+ expectImeVisible(TIMEOUT);
+ }
}
}
@@ -448,11 +432,64 @@
return new Pair<>(focusedEditTextRef.get(), testActivityRef.get());
}
- private View addChildWindow(Activity activity, WindowManager.LayoutParams attrs) {
- final WindowManager wm = activity.getSystemService(WindowManager.class);
- final View childViewRoot = new View(activity);
- childViewRoot.setVisibility(View.GONE);
- wm.addView(childViewRoot, attrs);
- return childViewRoot;
+ /**
+ * A utility class to pack the root {@link View} and its clean-up operation that is compatible
+ * with {@link AutoCloseable} protocol.
+ */
+ private static final class ChildWindowHolder implements AutoCloseable {
+ @NonNull
+ private final View mRootView;
+
+ private ChildWindowHolder(@NonNull View rootView) {
+ mRootView = rootView;
+ }
+
+ @NonNull
+ @AnyThread
+ View getRootView() {
+ return mRootView;
+ }
+
+ @Override
+ public void close() {
+ TestUtils.runOnMainSync(() -> mRootView.getContext()
+ .getSystemService(WindowManager.class).removeView(mRootView));
+ }
+ }
+
+ @NonNull
+ private ChildWindowHolder createChildBottomPanelWindowOnMain(Activity activity, int width,
+ int height, int windowFlags) {
+ return TestUtils.getOnMainSync(() -> {
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+ attrs.token = null;
+ attrs.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+ attrs.width = width;
+ attrs.height = height;
+ attrs.gravity = Gravity.BOTTOM;
+ attrs.flags = windowFlags;
+ final View childViewRoot = new View(activity);
+ activity.getSystemService(WindowManager.class).addView(childViewRoot, attrs);
+ return new ChildWindowHolder(childViewRoot);
+ });
+ }
+
+ @NonNull
+ private ChildWindowHolder createChildTransparentApplicationWindowOnMain(Activity activity,
+ int width, int height, int windowFlags, int fitInsetsTypes) {
+ return TestUtils.getOnMainSync(() -> {
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+ attrs.token = activity.getWindow().getAttributes().token;
+ attrs.type = WindowManager.LayoutParams.TYPE_APPLICATION;
+ attrs.width = width;
+ attrs.height = height;
+ attrs.format = PixelFormat.TRANSPARENT;
+ attrs.gravity = Gravity.NO_GRAVITY;
+ attrs.flags = windowFlags;
+ attrs.setFitInsetsTypes(fitInsetsTypes);
+ final View childViewRoot = new View(activity);
+ activity.getSystemService(WindowManager.class).addView(childViewRoot, attrs);
+ return new ChildWindowHolder(childViewRoot);
+ });
}
}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionBlockingMethodTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionBlockingMethodTest.java
deleted file mode 100644
index 2ac8a33..0000000
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionBlockingMethodTest.java
+++ /dev/null
@@ -1,956 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.inputmethod.cts;
-
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
-
-import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ClipDescription;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.ExtractedText;
-import android.view.inputmethod.ExtractedTextRequest;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputConnectionWrapper;
-import android.view.inputmethod.InputContentInfo;
-import android.view.inputmethod.cts.util.EndToEndImeTestBase;
-import android.view.inputmethod.cts.util.MockTestActivityUtil;
-import android.view.inputmethod.cts.util.TestActivity;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.filters.LargeTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.cts.mockime.ImeCommand;
-import com.android.cts.mockime.ImeEvent;
-import com.android.cts.mockime.ImeEventStream;
-import com.android.cts.mockime.ImeSettings;
-import com.android.cts.mockime.MockImeSession;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Function;
-
-/**
- * Ensures that blocking APIs in {@link InputConnection} are working as expected.
- *
- * <p>TODO(b/129012881): Reduce boilerplate code.</p>
- */
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class InputConnectionBlockingMethodTest extends EndToEndImeTestBase {
- private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
- private static final long LONG_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
- private static final long IMMEDIATE_TIMEOUT_NANO = TimeUnit.MILLISECONDS.toNanos(200);
-
- private static final String TEST_MARKER_PREFIX =
- "android.view.inputmethod.cts.InputConnectionBlockingMethodTest";
-
- private static String getTestMarker() {
- return TEST_MARKER_PREFIX + "/" + SystemClock.elapsedRealtimeNanos();
- }
-
- /**
- * A utility method to verify a method is called within a certain timeout period then block
- * it by {@link BlockingMethodVerifier#close()} is called.
- */
- private static final class BlockingMethodVerifier implements AutoCloseable {
- private final CountDownLatch mWaitUntilMethodCalled = new CountDownLatch(1);
- private final CountDownLatch mWaitUntilTestFinished = new CountDownLatch(1);
-
- /**
- * Used to notify when a method to be tested is called.
- */
- void onMethodCalled() {
- try {
- mWaitUntilMethodCalled.countDown();
- mWaitUntilTestFinished.await();
- } catch (InterruptedException e) {
- }
- }
-
- /**
- * Ensures that the method to be tested is called within {@param timeout}.
- *
- * @param message Message to be shown when the method is not called despite the expectation.
- * @param timeout Timeout in milliseconds.
- */
- void expectMethodCalled(@NonNull String message, long timeout) {
- try {
- assertTrue(message, mWaitUntilMethodCalled.await(timeout, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(message + e);
- }
- }
-
- /**
- * Unblock the method to be tested to avoid the test from being blocked forever.
- */
- @Override
- public void close() throws Exception {
- mWaitUntilTestFinished.countDown();
- }
- }
-
- /**
- * A test procedure definition for
- * {@link #testInputConnection(Function, TestProcedure, AutoCloseable)}.
- */
- @FunctionalInterface
- interface TestProcedure {
- /**
- * The test body of {@link #testInputConnection(Function, TestProcedure, AutoCloseable)}
- *
- * @param session {@link MockImeSession} to be used during this test.
- * @param stream {@link ImeEventStream} associated with {@code session}.
- */
- void run(@NonNull MockImeSession session, @NonNull ImeEventStream stream) throws Exception;
- }
-
- /**
- * Tries to trigger {@link com.android.cts.mockime.MockIme#onUnbindInput()} by showing another
- * Activity in a different process.
- */
- private void triggerUnbindInput() {
- final boolean isInstant = InstrumentationRegistry.getInstrumentation().getTargetContext()
- .getPackageManager().isInstantApp();
- MockTestActivityUtil.launchSync(isInstant, TIMEOUT);
- }
-
- /**
- * A utility method to run a unit test for {@link InputConnection}.
- *
- * <p>This utility method enables you to avoid boilerplate code when writing unit tests for
- * {@link InputConnection}.</p>
- *
- * @param inputConnectionWrapperProvider {@link Function} to install custom hooks to the
- * original {@link InputConnection}.
- * @param testProcedure Test body.
- */
- private void testInputConnection(
- Function<InputConnection, InputConnection> inputConnectionWrapperProvider,
- TestProcedure testProcedure) throws Exception {
- testInputConnection(inputConnectionWrapperProvider, testProcedure, null);
- }
-
- /**
- * A utility method to run a unit test for {@link InputConnection}.
- *
- * <p>This utility method enables you to avoid boilerplate code when writing unit tests for
- * {@link InputConnection}.</p>
- *
- * @param inputConnectionWrapperProvider {@link Function} to install custom hooks to the
- * original {@link InputConnection}.
- * @param testProcedure Test body.
- * @param closeable {@link AutoCloseable} object to be cleaned up after running test.
- */
- private void testInputConnection(
- Function<InputConnection, InputConnection> inputConnectionWrapperProvider,
- TestProcedure testProcedure, @Nullable AutoCloseable closeable) throws Exception {
- try (AutoCloseable closeableHolder = closeable;
- MockImeSession imeSession = MockImeSession.create(
- InstrumentationRegistry.getInstrumentation().getContext(),
- InstrumentationRegistry.getInstrumentation().getUiAutomation(),
- new ImeSettings.Builder())) {
- final AtomicBoolean isTestRunning = new AtomicBoolean(true);
- try {
- final ImeEventStream stream = imeSession.openEventStream();
-
- final String marker = getTestMarker();
- TestActivity.startSync(activity -> {
- final LinearLayout layout = new LinearLayout(activity);
- layout.setOrientation(LinearLayout.VERTICAL);
- final EditText editText = new EditText(activity) {
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- final InputConnection ic = super.onCreateInputConnection(outAttrs);
- // Fall back to the original InputConnection once the test is done.
- return isTestRunning.get()
- ? inputConnectionWrapperProvider.apply(ic) : ic;
- }
- };
- editText.setPrivateImeOptions(marker);
- editText.setHint("editText");
- editText.requestFocus();
-
- layout.addView(editText);
- activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
- return layout;
- });
-
- // Wait until the MockIme gets bound to the TestActivity.
- expectBindInput(stream, Process.myPid(), TIMEOUT);
-
- // Wait until "onStartInput" gets called for the EditText.
- expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
-
- testProcedure.run(imeSession, stream);
- } finally {
- isTestRunning.set(false);
- }
- }
- }
-
- /**
- * Ensures that {@code event}'s elapse time is less than the given threshold.
- *
- * @param event {@link ImeEvent} to be tested.
- * @param elapseNanoTimeThreshold threshold in nano sec.
- */
- private static void expectElapseTimeLessThan(@NonNull ImeEvent event,
- long elapseNanoTimeThreshold) {
- final long elapseNanoTime = event.getExitTimestamp() - event.getEnterTimestamp();
- if (elapseNanoTime > elapseNanoTimeThreshold) {
- fail(event.getEventName() + " took " + elapseNanoTime + " nsec,"
- + " which must be less than" + elapseNanoTimeThreshold + " nsec.");
- }
- }
-
- /**
- * Test {@link InputConnection#getTextAfterCursor(int, int)} works as expected.
- */
- @Test
- public void testGetTextAfterCursor() throws Exception {
- final int expectedN = 3;
- final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
- final String expectedResult = "89";
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public CharSequence getTextAfterCursor(int n, int flags) {
- assertEquals(expectedN, n);
- assertEquals(expectedFlags, flags);
- return expectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- final ImeCommand command = session.callGetTextAfterCursor(expectedN, expectedFlags);
- final CharSequence result =
- expectCommand(stream, command, TIMEOUT).getReturnCharSequenceValue();
- assertEquals(expectedResult, result);
- });
- }
-
- /**
- * Test {@link InputConnection#getTextAfterCursor(int, int)} fails after a system-defined
- * time-out even if the target app does not respond.
- */
- @Test
- public void testGetTextAfterCursorFailWithTimeout() throws Exception {
- final String unexpectedResult = "89";
- final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public CharSequence getTextAfterCursor(int n, int flags) {
- blocker.onMethodCalled();
- return unexpectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- final ImeCommand command = session.callGetTextAfterCursor(
- unexpectedResult.length(), InputConnection.GET_TEXT_WITH_STYLES);
- blocker.expectMethodCalled("IC#getTextAfterCursor() must be called back", TIMEOUT);
- final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
- assertTrue("When timeout happens, IC#getTextAfterCursor() returns null",
- result.isNullReturnValue());
- }, blocker);
- }
-
- /**
- * Test {@link InputConnection#getTextAfterCursor(int, int)} fail-fasts once unbindInput() is
- * issued.
- */
- @Test
- public void testGetTextAfterCursorFailFastAfterUnbindInput() throws Exception {
- final String unexpectedResult = "89";
- final AtomicBoolean methodCalled = new AtomicBoolean(false);
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public CharSequence getTextAfterCursor(int n, int flags) {
- methodCalled.set(true);
- return unexpectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- // Memorize the current InputConnection.
- expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
-
- // Let unbindInput happen.
- triggerUnbindInput();
- expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
-
- // Now IC#getTextAfterCursor() for the memorized IC should fail fast.
- final ImeEvent result = expectCommand(stream, session.callGetTextAfterCursor(
- unexpectedResult.length(), InputConnection.GET_TEXT_WITH_STYLES), TIMEOUT);
- assertTrue("Once unbindInput() happened, IC#getTextAfterCursor() returns null",
- result.isNullReturnValue());
- assertFalse("Once unbindInput() happened, IC#getTextAfterCursor() fails fast.",
- methodCalled.get());
- expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
- });
- }
-
- /**
- * Test {@link InputConnection#getTextBeforeCursor(int, int)} works as expected.
- */
- @Test
- public void testGetTextBeforeCursor() throws Exception {
- final int expectedN = 3;
- final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
- final String expectedResult = "123";
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public CharSequence getTextBeforeCursor(int n, int flags) {
- assertEquals(expectedN, n);
- assertEquals(expectedFlags, flags);
- return expectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- final ImeCommand command = session.callGetTextBeforeCursor(expectedN, expectedFlags);
- final CharSequence result =
- expectCommand(stream, command, TIMEOUT).getReturnCharSequenceValue();
- assertEquals(expectedResult, result);
- });
- }
-
- /**
- * Test {@link InputConnection#getTextBeforeCursor(int, int)} fails after a system-defined
- * time-out even if the target app does not respond.
- */
- @Test
- public void testGetTextBeforeCursorFailWithTimeout() throws Exception {
- final String unexpectedResult = "123";
- final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public CharSequence getTextBeforeCursor(int n, int flags) {
- blocker.onMethodCalled();
- return unexpectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- final ImeCommand command = session.callGetTextBeforeCursor(
- unexpectedResult.length(), InputConnection.GET_TEXT_WITH_STYLES);
- blocker.expectMethodCalled("IC#getTextBeforeCursor() must be called back", TIMEOUT);
- final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
- assertTrue("When timeout happens, IC#getTextBeforeCursor() returns null",
- result.isNullReturnValue());
- }, blocker);
- }
-
- /**
- * Test {@link InputConnection#getTextBeforeCursor(int, int)} fail-fasts once unbindInput() is
- * issued.
- */
- @Test
- public void testGetTextBeforeCursorFailFastAfterUnbindInput() throws Exception {
- final String unexpectedResult = "123";
- final AtomicBoolean methodCalled = new AtomicBoolean(false);
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public CharSequence getTextBeforeCursor(int n, int flags) {
- methodCalled.set(true);
- return unexpectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- // Memorize the current InputConnection.
- expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
-
- // Let unbindInput happen.
- triggerUnbindInput();
- expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
-
- // Now IC#getTextBeforeCursor() for the memorized IC should fail fast.
- final ImeEvent result = expectCommand(stream, session.callGetTextBeforeCursor(
- unexpectedResult.length(), InputConnection.GET_TEXT_WITH_STYLES), TIMEOUT);
- assertTrue("Once unbindInput() happened, IC#getTextBeforeCursor() returns null",
- result.isNullReturnValue());
- assertFalse("Once unbindInput() happened, IC#getTextBeforeCursor() fails fast.",
- methodCalled.get());
- expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
- });
- }
-
- /**
- * Test {@link InputConnection#getSelectedText(int)} works as expected.
- */
- @Test
- public void testGetSelectedText() throws Exception {
- final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
- final String expectedResult = "4567";
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public CharSequence getSelectedText(int flags) {
- assertEquals(expectedFlags, flags);
- return expectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- final ImeCommand command = session.callGetSelectedText(expectedFlags);
- final CharSequence result =
- expectCommand(stream, command, TIMEOUT).getReturnCharSequenceValue();
- assertEquals(expectedResult, result);
- });
- }
-
- /**
- * Test {@link InputConnection#getSelectedText(int)} fails after a system-defined time-out even
- * if the target app does not respond.
- */
- @Test
- public void testGetSelectedTextFailWithTimeout() throws Exception {
- final String unexpectedResult = "4567";
- final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public CharSequence getSelectedText(int flags) {
- blocker.onMethodCalled();
- return unexpectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- final ImeCommand command =
- session.callGetSelectedText(InputConnection.GET_TEXT_WITH_STYLES);
- blocker.expectMethodCalled("IC#getSelectedText() must be called back", TIMEOUT);
- final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
- assertTrue("When timeout happens, IC#getSelectedText() returns null",
- result.isNullReturnValue());
- }, blocker);
- }
-
- /**
- * Test {@link InputConnection#getSelectedText(int)} fail-fasts once unbindInput() is issued.
- */
- @Test
- public void testGetSelectedTextFailFastAfterUnbindInput() throws Exception {
- final String unexpectedResult = "4567";
- final AtomicBoolean methodCalled = new AtomicBoolean(false);
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public CharSequence getSelectedText(int flags) {
- methodCalled.set(true);
- return unexpectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- // Memorize the current InputConnection.
- expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
-
- // Let unbindInput happen.
- triggerUnbindInput();
- expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
-
- // Now IC#getSelectedText() for the memorized IC should fail fast.
- final ImeEvent result = expectCommand(stream, session.callGetSelectedText(
- InputConnection.GET_TEXT_WITH_STYLES), TIMEOUT);
- assertTrue("Once unbindInput() happened, IC#getSelectedText() returns null",
- result.isNullReturnValue());
- assertFalse("Once unbindInput() happened, IC#getSelectedText() fails fast.",
- methodCalled.get());
- expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
- });
- }
-
- /**
- * Test {@link InputConnection#getCursorCapsMode(int)} works as expected.
- */
- @Test
- public void testGetCursorCapsMode() throws Exception {
- final int expectedResult = EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
- final int expectedReqMode = TextUtils.CAP_MODE_SENTENCES | TextUtils.CAP_MODE_CHARACTERS
- | TextUtils.CAP_MODE_WORDS;
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public int getCursorCapsMode(int reqModes) {
- assertEquals(expectedReqMode, reqModes);
- return expectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- final ImeCommand command = session.callGetCursorCapsMode(expectedReqMode);
- final int result = expectCommand(stream, command, TIMEOUT).getReturnIntegerValue();
- assertEquals(expectedResult, result);
- });
- }
-
- /**
- * Test {@link InputConnection#getCursorCapsMode(int)} fails after a system-defined time-out
- * even if the target app does not respond.
- */
- @Test
- public void testGetCursorCapsModeFailWithTimeout() throws Exception {
- final int unexpectedResult = EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
- final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public int getCursorCapsMode(int reqModes) {
- blocker.onMethodCalled();
- return unexpectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- final ImeCommand command = session.callGetCursorCapsMode(TextUtils.CAP_MODE_WORDS);
- blocker.expectMethodCalled("IC#getCursorCapsMode() must be called back", TIMEOUT);
- final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
- assertEquals("When timeout happens, IC#getCursorCapsMode() returns 0",
- 0, result.getReturnIntegerValue());
- }, blocker);
- }
-
- /**
- * Test {@link InputConnection#getCursorCapsMode(int)} fail-fasts once unbindInput() is issued.
- */
- @Test
- public void testGetCursorCapsModeFailFastAfterUnbindInput() throws Exception {
- final int unexpectedResult = EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
- final AtomicBoolean methodCalled = new AtomicBoolean(false);
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public int getCursorCapsMode(int reqModes) {
- methodCalled.set(true);
- return unexpectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- // Memorize the current InputConnection.
- expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
-
- // Let unbindInput happen.
- triggerUnbindInput();
- expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
-
- // Now IC#getCursorCapsMode() for the memorized IC should fail fast.
- final ImeEvent result = expectCommand(stream,
- session.callGetCursorCapsMode(TextUtils.CAP_MODE_WORDS), TIMEOUT);
- assertEquals("Once unbindInput() happened, IC#getCursorCapsMode() returns 0",
- 0, result.getReturnIntegerValue());
- assertFalse("Once unbindInput() happened, IC#getCursorCapsMode() fails fast.",
- methodCalled.get());
- expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
- });
- }
-
- /**
- * Test {@link InputConnection#getExtractedText(ExtractedTextRequest, int)} works as expected.
- */
- @Test
- public void testGetExtractedText() throws Exception {
- final ExtractedTextRequest expectedRequest = ExtractedTextRequestTest.createForTest();
- final int expectedFlags = InputConnection.GET_EXTRACTED_TEXT_MONITOR;
- final ExtractedText expectedResult = ExtractedTextTest.createForTest();
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
- assertEquals(expectedFlags, flags);
- ExtractedTextRequestTest.assertTestInstance(request);
- return expectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- final ImeCommand command = session.callGetExtractedText(expectedRequest, expectedFlags);
- final ExtractedText result =
- expectCommand(stream, command, TIMEOUT).getReturnParcelableValue();
- ExtractedTextTest.assertTestInstance(result);
- });
- }
-
- /**
- * Test {@link InputConnection#getExtractedText(ExtractedTextRequest, int)} fails after a
- * system-defined time-out even if the target app does not respond.
- */
- @Test
- public void testGetExtractedTextFailWithTimeout() throws Exception {
- final ExtractedText unexpectedResult = ExtractedTextTest.createForTest();
- final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
- blocker.onMethodCalled();
- return unexpectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- final ImeCommand command = session.callGetExtractedText(
- ExtractedTextRequestTest.createForTest(),
- InputConnection.GET_EXTRACTED_TEXT_MONITOR);
- blocker.expectMethodCalled("IC#getExtractedText() must be called back", TIMEOUT);
- final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
- assertTrue("When timeout happens, IC#getExtractedText() returns null",
- result.isNullReturnValue());
- }, blocker);
- }
-
- /**
- * Test {@link InputConnection#getExtractedText(ExtractedTextRequest, int)} fail-fasts once
- * unbindInput() is issued.
- */
- @Test
- public void testGetExtractedTextFailFastAfterUnbindInput() throws Exception {
- final ExtractedText unexpectedResult = ExtractedTextTest.createForTest();
- final AtomicBoolean methodCalled = new AtomicBoolean(false);
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
- methodCalled.set(true);
- return unexpectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- // Memorize the current InputConnection.
- expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
-
- // Let unbindInput happen.
- triggerUnbindInput();
- expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
-
- // Now IC#getExtractedText() for the memorized IC should fail fast.
- final ImeEvent result = expectCommand(stream, session.callGetExtractedText(
- ExtractedTextRequestTest.createForTest(),
- InputConnection.GET_EXTRACTED_TEXT_MONITOR), TIMEOUT);
- assertTrue("Once unbindInput() happened, IC#getExtractedText() returns null",
- result.isNullReturnValue());
- assertFalse("Once unbindInput() happened, IC#getExtractedText() fails fast.",
- methodCalled.get());
- expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
- });
- }
-
- /**
- * Test {@link InputConnection#requestCursorUpdates(int)} works as expected.
- */
- @Test
- public void testRequestCursorUpdates() throws Exception {
- final int expectedFlags = InputConnection.CURSOR_UPDATE_IMMEDIATE;
- final boolean expectedResult = true;
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public boolean requestCursorUpdates(int cursorUpdateMode) {
- assertEquals(expectedFlags, cursorUpdateMode);
- return expectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- final ImeCommand command = session.callRequestCursorUpdates(expectedFlags);
- assertTrue(expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
- });
- }
-
- /**
- * Test {@link InputConnection#requestCursorUpdates(int)} fails after a system-defined time-out
- * even if the target app does not respond.
- */
- @Test
- public void testRequestCursorUpdatesFailWithTimeout() throws Exception {
- final boolean unexpectedResult = true;
- final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public boolean requestCursorUpdates(int cursorUpdateMode) {
- blocker.onMethodCalled();
- return unexpectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- final ImeCommand command = session.callRequestCursorUpdates(
- InputConnection.CURSOR_UPDATE_IMMEDIATE);
- blocker.expectMethodCalled("IC#requestCursorUpdates() must be called back", TIMEOUT);
- final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
- assertFalse("When timeout happens, IC#requestCursorUpdates() returns false",
- result.getReturnBooleanValue());
- }, blocker);
- }
-
- /**
- * Test {@link InputConnection#requestCursorUpdates(int)} fail-fasts once unbindInput() is
- * issued.
- */
- @Test
- public void testRequestCursorUpdatesFailFastAfterUnbindInput() throws Exception {
- final boolean unexpectedResult = true;
- final AtomicBoolean methodCalled = new AtomicBoolean(false);
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public boolean requestCursorUpdates(int cursorUpdateMode) {
- methodCalled.set(true);
- return unexpectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- // Memorize the current InputConnection.
- expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
-
- // Let unbindInput happen.
- triggerUnbindInput();
- expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
-
- // Now IC#requestCursorUpdates() for the memorized IC should fail fast.
- final ImeEvent result = expectCommand(stream, session.callRequestCursorUpdates(
- InputConnection.CURSOR_UPDATE_IMMEDIATE), TIMEOUT);
- assertFalse("Once unbindInput() happened, IC#requestCursorUpdates() returns false",
- result.getReturnBooleanValue());
- assertFalse("Once unbindInput() happened, IC#requestCursorUpdates() fails fast.",
- methodCalled.get());
- expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
- });
- }
-
- /**
- * Test {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} works as expected.
- */
- @Test
- public void testCommitContent() throws Exception {
- final InputContentInfo expectedInputContentInfo = new InputContentInfo(
- Uri.parse("content://com.example/path"),
- new ClipDescription("sample content", new String[]{"image/png"}),
- Uri.parse("https://example.com"));
- final Bundle expectedOpt = new Bundle();
- final String expectedOptKey = "testKey";
- final int expectedOptValue = 42;
- expectedOpt.putInt(expectedOptKey, expectedOptValue);
- final int expectedFlags = InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
- final boolean expectedResult = true;
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public boolean commitContent(InputContentInfo inputContentInfo, int flags,
- Bundle opts) {
- assertEquals(expectedInputContentInfo.getContentUri(),
- inputContentInfo.getContentUri());
- assertEquals(expectedFlags, flags);
- assertEquals(expectedOpt.getInt(expectedOptKey), opts.getInt(expectedOptKey));
- return expectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- final ImeCommand command =
- session.callCommitContent(expectedInputContentInfo, expectedFlags, expectedOpt);
- assertTrue(expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
- });
- }
-
- /**
- * Test {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} fails after a
- * system-defined time-out even if the target app does not respond.
- */
- @Test
- public void testCommitContentFailWithTimeout() throws Exception {
- final boolean unexpectedResult = true;
- final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public boolean commitContent(InputContentInfo inputContentInfo, int flags,
- Bundle opts) {
- blocker.onMethodCalled();
- return unexpectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- final ImeCommand command = session.callCommitContent(
- new InputContentInfo(Uri.parse("content://com.example/path"),
- new ClipDescription("sample content", new String[]{"image/png"}),
- Uri.parse("https://example.com")), 0, null);
- blocker.expectMethodCalled("IC#commitContent() must be called back", TIMEOUT);
- final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
- assertFalse("When timeout happens, IC#commitContent() returns false",
- result.getReturnBooleanValue());
- }, blocker);
- }
-
- /**
- * Test {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} fail-fasts once
- * unbindInput() is issued.
- */
- @Test
- public void testCommitContentFailFastAfterUnbindInput() throws Exception {
- final boolean unexpectedResult = true;
- final AtomicBoolean methodCalled = new AtomicBoolean(false);
-
- final class Wrapper extends InputConnectionWrapper {
- private Wrapper(InputConnection target) {
- super(target, false);
- }
-
- @Override
- public boolean commitContent(InputContentInfo inputContentInfo, int flags,
- Bundle opts) {
- methodCalled.set(true);
- return unexpectedResult;
- }
- }
-
- testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
- // Memorize the current InputConnection.
- expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
-
- // Let unbindInput happen.
- triggerUnbindInput();
- expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
-
- // Now IC#getTextAfterCursor() for the memorized IC should fail fast.
- final ImeEvent result = expectCommand(stream, session.callCommitContent(
- new InputContentInfo(Uri.parse("content://com.example/path"),
- new ClipDescription("sample content", new String[]{"image/png"}),
- Uri.parse("https://example.com")), 0, null), TIMEOUT);
- assertFalse("Once unbindInput() happened, IC#commitContent() returns false",
- result.getReturnBooleanValue());
- assertFalse("Once unbindInput() happened, IC#commitContent() fails fast.",
- methodCalled.get());
- expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
- });
- }
-}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java
new file mode 100644
index 0000000..28e9f92
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java
@@ -0,0 +1,3891 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Process;
+import android.os.SystemClock;
+import android.text.Annotation;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputConnectionWrapper;
+import android.view.inputmethod.InputContentInfo;
+import android.view.inputmethod.SurroundingText;
+import android.view.inputmethod.TextAttribute;
+import android.view.inputmethod.TextSnapshot;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.MockTestActivityUtil;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.inputmethod.LegacyImeClientTestUtils;
+import com.android.cts.mockime.ImeCommand;
+import com.android.cts.mockime.ImeEvent;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import com.google.common.truth.Correspondence;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * Provides basic tests for APIs defined in {@link InputConnection}.
+ *
+ * <p>TODO(b/193535269): Clean up boilerplate code around mocking InputConnection.</p>
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class InputConnectionEndToEndTest extends EndToEndImeTestBase {
+ private static final long TIME_SLICE = TimeUnit.MILLISECONDS.toMillis(125);
+ private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+ private static final long EXPECTED_NOT_CALLED_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+ private static final long LONG_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
+ private static final long IMMEDIATE_TIMEOUT_NANO = TimeUnit.MILLISECONDS.toNanos(200);
+
+ private static final String TEST_MARKER_PREFIX =
+ "android.view.inputmethod.cts.InputConnectionEndToEndTest";
+
+ private static String getTestMarker() {
+ return TEST_MARKER_PREFIX + "/" + SystemClock.elapsedRealtimeNanos();
+ }
+
+ /**
+ * A utility method to verify a method is called within a certain timeout period then block
+ * it by {@link BlockingMethodVerifier#close()} is called.
+ */
+ private static final class BlockingMethodVerifier implements AutoCloseable {
+ private final CountDownLatch mWaitUntilMethodCalled = new CountDownLatch(1);
+ private final CountDownLatch mWaitUntilTestFinished = new CountDownLatch(1);
+
+ /**
+ * Used to notify when a method to be tested is called.
+ */
+ void onMethodCalled() {
+ try {
+ mWaitUntilMethodCalled.countDown();
+ mWaitUntilTestFinished.await();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ /**
+ * Ensures that the method to be tested is called within {@param timeout}.
+ *
+ * @param message Message to be shown when the method is not called despite the expectation.
+ * @param timeout Timeout in milliseconds.
+ */
+ void expectMethodCalled(@NonNull String message, long timeout) {
+ try {
+ assertTrue(message, mWaitUntilMethodCalled.await(timeout, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(message + e);
+ }
+ }
+
+ /**
+ * Unblock the method to be tested to avoid the test from being blocked forever.
+ */
+ @Override
+ public void close() throws Exception {
+ mWaitUntilTestFinished.countDown();
+ }
+ }
+
+ /**
+ * A utility method to verify that a method is called with a certain set of parameters.
+ */
+ private static final class MethodCallVerifier {
+ private final AtomicReference<Bundle> mArgs = new AtomicReference<>();
+ private final AtomicInteger mCallCount = new AtomicInteger(0);
+
+ @AnyThread
+ void reset() {
+ mArgs.set(null);
+ mCallCount.set(0);
+ }
+
+ /**
+ * Used to record when a method to be tested is called.
+ *
+ * @param argumentsRecorder a {@link Consumer} to capture method parameters.
+ */
+ void onMethodCalled(@NonNull Consumer<Bundle> argumentsRecorder) {
+ final Bundle bundle = new Bundle();
+ argumentsRecorder.accept(bundle);
+ mArgs.set(bundle);
+ mCallCount.incrementAndGet();
+ }
+
+ /**
+ * Used to assert captured parameters later.
+ *
+ * @param argumentsVerifier a {@link Consumer} to verify method arguments.
+ * @throws AssertionError when {@link #onMethodCalled(Consumer)} was not called only once.
+ */
+ void assertCalledOnce(@NonNull Consumer<Bundle> argumentsVerifier) {
+ assertEquals(1, mCallCount.get());
+ final Bundle bundle = mArgs.get();
+ assertNotNull(bundle);
+ argumentsVerifier.accept(bundle);
+ }
+
+ /**
+ * Ensures that the method to be tested is called within {@param timeout}.
+ *
+ * @param argumentsVerifier a {@link Consumer} to verify method arguments.
+ * @param timeout timeout in millisecond
+ * @throws AssertionError when {@link #onMethodCalled(Consumer)} was not called only once.
+ */
+ void expectCalledOnce(@NonNull Consumer<Bundle> argumentsVerifier, long timeout) {
+ // Currently using busy-wait because CountDownLatch is not compatible with reset().
+ // TODO: Consider using other more efficient operation.
+ long remainingTime = timeout;
+ while (mCallCount.get() == 0) {
+ if (remainingTime < 0) {
+ fail("The method must be called, but was not within" + timeout + " msec.");
+ }
+ SystemClock.sleep(TIME_SLICE);
+ remainingTime -= TIME_SLICE;
+ }
+ assertEquals(1, mCallCount.get());
+ final Bundle bundle = mArgs.get();
+ assertNotNull(bundle);
+ argumentsVerifier.accept(bundle);
+ }
+
+ /**
+ * Used to assert that {@link #onMethodCalled(Consumer)} was never called.
+ *
+ * @param callCountVerificationMessage A message to be used when the assertion fails.
+ */
+ void assertNotCalled(@Nullable String callCountVerificationMessage) {
+ if (callCountVerificationMessage != null) {
+ assertEquals(callCountVerificationMessage, 0, mCallCount.get());
+ } else {
+ assertEquals(0, mCallCount.get());
+ }
+ }
+
+ /**
+ * Ensures that the method to be tested is not called within {@param timeout}.
+ *
+ * @param callCountVerificationMessage A message to be used when the assertion fails.
+ * @param timeout timeout in millisecond
+ */
+ void expectNotCalled(@Nullable String callCountVerificationMessage, long timeout) {
+ // Currently using busy-wait because CountDownLatch is not compatible with reset().
+ // TODO: Consider using other more efficient operation.
+ long remainingTime = timeout;
+ while (true) {
+ if (mCallCount.get() != 0) {
+ fail("The method must not be called. params=" + evaluateBundle(mArgs.get()));
+ }
+ if (remainingTime < 0) {
+ break; // This is indeed an expected scenario, not an error.
+ }
+ SystemClock.sleep(TIME_SLICE);
+ remainingTime -= TIME_SLICE;
+ }
+ if (callCountVerificationMessage != null) {
+ assertEquals(callCountVerificationMessage, 0, mCallCount.get());
+ } else {
+ assertEquals(0, mCallCount.get());
+ }
+ }
+
+ /**
+ * Recursively evaluate {@link Bundle} so that {@link Bundle#toString()} can print all the
+ * nested {@link Bundle} objects.
+ *
+ * @param bundle {@link Bundle} to recursively evaluate.
+ * @return the {@code bundle} object passed.
+ */
+ @Nullable
+ private static Bundle evaluateBundle(@Nullable Bundle bundle) {
+ if (bundle != null) {
+ for (String key : bundle.keySet()) {
+ final Object value = bundle.get(key);
+ if (value instanceof Bundle) {
+ evaluateBundle((Bundle) value);
+ }
+ }
+ }
+ return bundle;
+ }
+ }
+
+ /**
+ * A test procedure definition for
+ * {@link #testInputConnection(Function, TestProcedure, AutoCloseable)}.
+ */
+ @FunctionalInterface
+ interface TestProcedure {
+ /**
+ * The test body of {@link #testInputConnection(Function, TestProcedure, AutoCloseable)}
+ *
+ * @param session {@link MockImeSession} to be used during this test.
+ * @param stream {@link ImeEventStream} associated with {@code session}.
+ */
+ void run(@NonNull MockImeSession session, @NonNull ImeEventStream stream) throws Exception;
+ }
+
+ /**
+ * Tries to trigger {@link com.android.cts.mockime.MockIme#onUnbindInput()} by showing another
+ * Activity in a different process.
+ */
+ private void triggerUnbindInput() {
+ final boolean isInstant = InstrumentationRegistry.getInstrumentation().getTargetContext()
+ .getPackageManager().isInstantApp();
+ MockTestActivityUtil.launchSync(isInstant, TIMEOUT);
+ }
+
+ /**
+ * A utility method to run a unit test for {@link InputConnection}.
+ *
+ * <p>This utility method enables you to avoid boilerplate code when writing unit tests for
+ * {@link InputConnection}.</p>
+ *
+ * @param inputConnectionWrapperProvider {@link Function} to install custom hooks to the
+ * original {@link InputConnection}.
+ * @param testProcedure Test body.
+ */
+ private void testInputConnection(
+ Function<InputConnection, InputConnection> inputConnectionWrapperProvider,
+ TestProcedure testProcedure) throws Exception {
+ testInputConnection(inputConnectionWrapperProvider, testProcedure, null);
+ }
+
+ /**
+ * A utility method to run a unit test for {@link InputConnection} that is as-if built with
+ * {@link android.os.Build.VERSION_CODES#CUPCAKE} SDK.
+ *
+ * <p>This helps you to test the situation where IMEs' calling newly added
+ * {@link InputConnection} APIs would be fallen back to its default interface method or could be
+ * causing {@link java.lang.AbstractMethodError} unless specially handled.
+ *
+ * @param testProcedure Test body.
+ */
+ private void testMinimallyImplementedInputConnection(TestProcedure testProcedure)
+ throws Exception {
+ testInputConnection(
+ ic -> LegacyImeClientTestUtils.createMinimallyImplementedNoOpInputConnection(),
+ testProcedure, null);
+ }
+
+ /**
+ * A utility method to run a unit test for {@link InputConnection}.
+ *
+ * <p>This utility method enables you to avoid boilerplate code when writing unit tests for
+ * {@link InputConnection}.</p>
+ *
+ * @param inputConnectionWrapperProvider {@link Function} to install custom hooks to the
+ * original {@link InputConnection}.
+ * @param testProcedure Test body.
+ * @param closeable {@link AutoCloseable} object to be cleaned up after running test.
+ */
+ private void testInputConnection(
+ Function<InputConnection, InputConnection> inputConnectionWrapperProvider,
+ TestProcedure testProcedure, @Nullable AutoCloseable closeable) throws Exception {
+ try (AutoCloseable closeableHolder = closeable;
+ MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder())) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ final String marker = getTestMarker();
+ TestActivity.startSync(activity -> {
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ // Just to be conservative, we explicitly check MockImeSession#isActive() here when
+ // injecting our custom InputConnection implementation.
+ final EditText editText = new EditText(activity) {
+ @Override
+ public boolean onCheckIsTextEditor() {
+ return imeSession.isActive();
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ if (imeSession.isActive()) {
+ final InputConnection ic = super.onCreateInputConnection(outAttrs);
+ return inputConnectionWrapperProvider.apply(ic);
+ }
+ return null;
+ }
+ };
+
+ editText.setPrivateImeOptions(marker);
+ editText.setHint("editText");
+ editText.requestFocus();
+
+ layout.addView(editText);
+ activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+ return layout;
+ });
+
+ // Wait until the MockIme gets bound to the TestActivity.
+ expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+ // Wait until "onStartInput" gets called for the EditText.
+ expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
+ testProcedure.run(imeSession, stream);
+ }
+ }
+
+ /**
+ * Ensures that {@code event}'s elapse time is less than the given threshold.
+ *
+ * @param event {@link ImeEvent} to be tested.
+ * @param elapseNanoTimeThreshold threshold in nano sec.
+ */
+ private static void expectElapseTimeLessThan(@NonNull ImeEvent event,
+ long elapseNanoTimeThreshold) {
+ final long elapseNanoTime = event.getExitTimestamp() - event.getEnterTimestamp();
+ if (elapseNanoTime > elapseNanoTimeThreshold) {
+ fail(event.getEventName() + " took " + elapseNanoTime + " nsec,"
+ + " which must be less than" + elapseNanoTimeThreshold + " nsec.");
+ }
+ }
+
+ @Nullable
+ private static CharSequence createTestCharSequence(@Nullable String text,
+ @Nullable Annotation annotation) {
+ if (text == null) {
+ return null;
+ }
+ final SpannableStringBuilder sb = new SpannableStringBuilder(text);
+ if (annotation != null) {
+ sb.setSpan(annotation, 0, sb.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ return sb;
+ }
+
+ private static void assertEqualsForTestCharSequence(@Nullable CharSequence expected,
+ @Nullable CharSequence actual) {
+ assertEquals(Objects.toString(expected), Objects.toString(actual));
+ final Function<CharSequence, List<Annotation>> toAnnotations = cs -> {
+ if (cs instanceof Spanned) {
+ final Spanned spanned = (Spanned) cs;
+ return Arrays.asList(spanned.getSpans(0, cs.length(), Annotation.class));
+ }
+ return Collections.emptyList();
+ };
+ assertThat(toAnnotations.apply(actual)).comparingElementsUsing(Correspondence.transforming(
+ (Annotation annotation) -> Pair.create(annotation.getKey(), annotation.getValue()),
+ (Annotation annotation) -> Pair.create(annotation.getKey(), annotation.getValue()),
+ "has the same Key/Value as"))
+ .containsExactlyElementsIn(toAnnotations.apply(expected));
+ }
+
+ /**
+ * Test {@link InputConnection#getTextAfterCursor(int, int)} works as expected.
+ */
+ @Test
+ public void testGetTextAfterCursor() throws Exception {
+ final int expectedN = 3;
+ final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
+ final CharSequence expectedResult =
+ createTestCharSequence("89", new Annotation("command", "getTextAfterCursor"));
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public CharSequence getTextAfterCursor(int n, int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("n", n);
+ args.putInt("flags", flags);
+ });
+ return expectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetTextAfterCursor(expectedN, expectedFlags);
+ final CharSequence result =
+ expectCommand(stream, command, TIMEOUT).getReturnCharSequenceValue();
+ assertEqualsForTestCharSequence(expectedResult, result);
+ methodCallVerifier.assertCalledOnce(args -> {
+ assertEquals(expectedN, args.get("n"));
+ assertEquals(expectedFlags, args.get("flags"));
+ });
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getTextAfterCursor(int, int)} fails when a negative
+ * {@code length} is passed. See Bug 169114026 for background.
+ */
+ @Test
+ public void testGetTextAfterCursorFailWithNegativeLength() throws Exception {
+ final String unexpectedResult = "123";
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public CharSequence getTextAfterCursor(int n, int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("n", n);
+ args.putInt("flags", flags);
+ });
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetTextAfterCursor(-1, 0);
+ final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+ assertTrue("IC#getTextAfterCursor() returns null for a negative length.",
+ result.isNullReturnValue());
+ methodCallVerifier.expectNotCalled(
+ "IC#getTextAfterCursor() will not be triggered with a negative length.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getTextAfterCursor(int, int)} fails after a system-defined
+ * time-out even if the target app does not respond.
+ */
+ @Test
+ public void testGetTextAfterCursorFailWithTimeout() throws Exception {
+ final int expectedN = 3;
+ final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
+ final String unexpectedResult = "89";
+ final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public CharSequence getTextAfterCursor(int n, int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("n", n);
+ args.putInt("flags", flags);
+ });
+ blocker.onMethodCalled();
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetTextAfterCursor(expectedN, expectedFlags);
+ blocker.expectMethodCalled("IC#getTextAfterCursor() must be called back", TIMEOUT);
+ final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+ assertTrue("When timeout happens, IC#getTextAfterCursor() returns null",
+ result.isNullReturnValue());
+ methodCallVerifier.assertCalledOnce(args -> {
+ assertEquals(expectedN, args.get("n"));
+ assertEquals(expectedFlags, args.get("flags"));
+ });
+ }, blocker);
+ }
+
+ /**
+ * Test {@link InputConnection#getTextAfterCursor(int, int)} fail-fasts once unbindInput() is
+ * issued.
+ */
+ @Test
+ public void testGetTextAfterCursorFailFastAfterUnbindInput() throws Exception {
+ final String unexpectedResult = "89";
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public CharSequence getTextAfterCursor(int n, int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("n", n);
+ args.putInt("flags", flags);
+ });
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now IC#getTextAfterCursor() for the memorized IC should fail fast.
+ final ImeEvent result = expectCommand(stream, session.callGetTextAfterCursor(
+ unexpectedResult.length(), InputConnection.GET_TEXT_WITH_STYLES), TIMEOUT);
+ assertTrue("Once unbindInput() happened, IC#getTextAfterCursor() returns null",
+ result.isNullReturnValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+ methodCallVerifier.assertNotCalled(
+ "Once unbindInput() happened, IC#getTextAfterCursor() fails fast.");
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getTextBeforeCursor(int, int)} works as expected.
+ */
+ @Test
+ public void testGetTextBeforeCursor() throws Exception {
+ final int expectedN = 3;
+ final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
+ final CharSequence expectedResult =
+ createTestCharSequence("123", new Annotation("command", "getTextBeforeCursor"));
+
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public CharSequence getTextBeforeCursor(int n, int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("n", n);
+ args.putInt("flags", flags);
+ });
+ return expectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetTextBeforeCursor(expectedN, expectedFlags);
+ final CharSequence result =
+ expectCommand(stream, command, TIMEOUT).getReturnCharSequenceValue();
+ assertEqualsForTestCharSequence(expectedResult, result);
+ methodCallVerifier.assertCalledOnce(args -> {
+ assertEquals(expectedN, args.get("n"));
+ assertEquals(expectedFlags, args.get("flags"));
+ });
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getTextBeforeCursor(int, int)} fails when a negative
+ * {@code length} is passed. See Bug 169114026 for background.
+ */
+ @Test
+ public void testGetTextBeforeCursorFailWithNegativeLength() throws Exception {
+ final String unexpectedResult = "123";
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public CharSequence getTextBeforeCursor(int n, int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("n", n);
+ args.putInt("flags", flags);
+ });
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetTextBeforeCursor(-1, 0);
+ final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+ assertTrue("IC#getTextBeforeCursor() returns null for a negative length.",
+ result.isNullReturnValue());
+ methodCallVerifier.expectNotCalled(
+ "IC#getTextBeforeCursor() will not be triggered with a negative length.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getTextBeforeCursor(int, int)} fails after a system-defined
+ * time-out even if the target app does not respond.
+ */
+ @Test
+ public void testGetTextBeforeCursorFailWithTimeout() throws Exception {
+ final int expectedN = 3;
+ final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
+ final String unexpectedResult = "123";
+ final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public CharSequence getTextBeforeCursor(int n, int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("n", n);
+ args.putInt("flags", flags);
+ });
+ blocker.onMethodCalled();
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetTextBeforeCursor(expectedN, expectedFlags);
+ blocker.expectMethodCalled("IC#getTextBeforeCursor() must be called back", TIMEOUT);
+ final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+ assertTrue("When timeout happens, IC#getTextBeforeCursor() returns null",
+ result.isNullReturnValue());
+ methodCallVerifier.assertCalledOnce(args -> {
+ assertEquals(expectedN, args.get("n"));
+ assertEquals(expectedFlags, args.get("flags"));
+ });
+ }, blocker);
+ }
+
+ /**
+ * Test {@link InputConnection#getTextBeforeCursor(int, int)} fail-fasts once unbindInput() is
+ * issued.
+ */
+ @Test
+ public void testGetTextBeforeCursorFailFastAfterUnbindInput() throws Exception {
+ final String unexpectedResult = "123";
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public CharSequence getTextBeforeCursor(int n, int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("n", n);
+ args.putInt("flags", flags);
+ });
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now IC#getTextBeforeCursor() for the memorized IC should fail fast.
+ final ImeEvent result = expectCommand(stream, session.callGetTextBeforeCursor(
+ unexpectedResult.length(), InputConnection.GET_TEXT_WITH_STYLES), TIMEOUT);
+ assertTrue("Once unbindInput() happened, IC#getTextBeforeCursor() returns null",
+ result.isNullReturnValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+ methodCallVerifier.assertNotCalled(
+ "Once unbindInput() happened, IC#getTextBeforeCursor() fails fast.");
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getSelectedText(int)} works as expected.
+ */
+ @Test
+ public void testGetSelectedText() throws Exception {
+ final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
+ final CharSequence expectedResult =
+ createTestCharSequence("4567", new Annotation("command", "getSelectedText"));
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public CharSequence getSelectedText(int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("flags", flags);
+ });
+ assertEquals(expectedFlags, flags);
+ return expectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetSelectedText(expectedFlags);
+ final CharSequence result =
+ expectCommand(stream, command, TIMEOUT).getReturnCharSequenceValue();
+ assertEqualsForTestCharSequence(expectedResult, result);
+ methodCallVerifier.assertCalledOnce(args -> {
+ assertEquals(expectedFlags, args.get("flags"));
+ });
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getSelectedText(int)} fails after a system-defined time-out even
+ * if the target app does not respond.
+ */
+ @Test
+ public void testGetSelectedTextFailWithTimeout() throws Exception {
+ final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
+ final String unexpectedResult = "4567";
+ final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public CharSequence getSelectedText(int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("flags", flags);
+ });
+ blocker.onMethodCalled();
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command =
+ session.callGetSelectedText(InputConnection.GET_TEXT_WITH_STYLES);
+ blocker.expectMethodCalled("IC#getSelectedText() must be called back", TIMEOUT);
+ final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+ assertTrue("When timeout happens, IC#getSelectedText() returns null",
+ result.isNullReturnValue());
+ methodCallVerifier.assertCalledOnce(args -> {
+ assertEquals(expectedFlags, args.get("flags"));
+ });
+ }, blocker);
+ }
+
+ /**
+ * Test {@link InputConnection#getSelectedText(int)} fail-fasts once unbindInput() is issued.
+ */
+ @Test
+ public void testGetSelectedTextFailFastAfterUnbindInput() throws Exception {
+ final String unexpectedResult = "4567";
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public CharSequence getSelectedText(int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("flags", flags);
+ });
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now IC#getSelectedText() for the memorized IC should fail fast.
+ final ImeEvent result = expectCommand(stream, session.callGetSelectedText(
+ InputConnection.GET_TEXT_WITH_STYLES), TIMEOUT);
+ assertTrue("Once unbindInput() happened, IC#getSelectedText() returns null",
+ result.isNullReturnValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+ methodCallVerifier.assertNotCalled(
+ "Once unbindInput() happened, IC#getSelectedText() fails fast.");
+ });
+ }
+
+ /**
+ * Verify that {@link InputConnection#getSelectedText(int)} returns {@code null} when the target
+ * app does not implement it. This can happen if the app was built before
+ * {@link android.os.Build.VERSION_CODES#GINGERBREAD}.
+ */
+ @Test
+ public void testGetSelectedTextFailWithMethodMissing() throws Exception {
+ testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetSelectedText(0);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ assertTrue("Currently getSelectedText() returns null when the target app does not"
+ + " implement it.", result.isNullReturnValue());
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getSurroundingText(int, int, int)} works as expected.
+ */
+ @Test
+ public void testGetSurroundingText() throws Exception {
+ final int expectedBeforeLength = 3;
+ final int expectedAfterLength = 4;
+ final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
+ final CharSequence expectedText =
+ createTestCharSequence("012345", new Annotation("command", "getSurroundingText"));
+ final SurroundingText expectedResult = new SurroundingText(expectedText, 1, 2, 0);
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public SurroundingText getSurroundingText(int beforeLength, int afterLength,
+ int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("beforeLength", beforeLength);
+ args.putInt("afterLength", afterLength);
+ args.putInt("flags", flags);
+ });
+ return expectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetSurroundingText(expectedBeforeLength,
+ expectedAfterLength, expectedFlags);
+ final SurroundingText result =
+ expectCommand(stream, command, TIMEOUT).getReturnParcelableValue();
+ assertEqualsForTestCharSequence(expectedResult.getText(), result.getText());
+ assertEquals(expectedResult.getSelectionStart(), result.getSelectionStart());
+ assertEquals(expectedResult.getSelectionEnd(), result.getSelectionEnd());
+ assertEquals(expectedResult.getOffset(), result.getOffset());
+ methodCallVerifier.assertCalledOnce(args -> {
+ assertEquals(expectedBeforeLength, args.get("beforeLength"));
+ assertEquals(expectedAfterLength, args.get("afterLength"));
+ assertEquals(expectedFlags, args.get("flags"));
+ });
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getSurroundingText(int, int, int)} fails when a nagative
+ * {@code afterLength} is passed. See Bug 169114026 for background.
+ */
+ @Test
+ public void testGetSurroundingTextFailWithNegativeAfterLength() throws Exception {
+ final SurroundingText unexpectedResult = new SurroundingText("012345", 1, 2, 0);
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public SurroundingText getSurroundingText(int beforeLength, int afterLength,
+ int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("beforeLength", beforeLength);
+ args.putInt("afterLength", afterLength);
+ args.putInt("flags", flags);
+ });
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetSurroundingText(1, -1, 0);
+ final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+ assertTrue("IC#getSurroundingText() returns null for a negative afterLength.",
+ result.isNullReturnValue());
+ methodCallVerifier.expectNotCalled(
+ "IC#getSurroundingText() will not be triggered with a negative afterLength.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getSurroundingText(int, int, int)} fails when a negative
+ * {@code beforeLength} is passed. See Bug 169114026 for background.
+ */
+ @Test
+ public void testGetSurroundingTextFailWithNegativeBeforeLength() throws Exception {
+ final SurroundingText unexpectedResult = new SurroundingText("012345", 1, 2, 0);
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public SurroundingText getSurroundingText(int beforeLength, int afterLength,
+ int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("beforeLength", beforeLength);
+ args.putInt("afterLength", afterLength);
+ args.putInt("flags", flags);
+ });
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetSurroundingText(-1, 1, 0);
+ final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+ assertTrue("IC#getSurroundingText() returns null for a negative beforeLength.",
+ result.isNullReturnValue());
+ methodCallVerifier.expectNotCalled(
+ "IC#getSurroundingText() will not be triggered with a negative beforeLength.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getSurroundingText(int, int, int)} fails after a system-defined
+ * time-out even if the target app does not respond.
+ */
+ @Test
+ public void testGetSurroundingTextFailWithTimeout() throws Exception {
+ final int expectedBeforeLength = 3;
+ final int expectedAfterLength = 4;
+ final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
+ final SurroundingText unexpectedResult = new SurroundingText("012345", 1, 2, 0);
+
+ final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public SurroundingText getSurroundingText(int beforeLength, int afterLength,
+ int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("beforeLength", beforeLength);
+ args.putInt("afterLength", afterLength);
+ args.putInt("flags", flags);
+ });
+ blocker.onMethodCalled();
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetSurroundingText(expectedBeforeLength,
+ expectedAfterLength, expectedFlags);
+ blocker.expectMethodCalled("IC#getSurroundingText() must be called back", TIMEOUT);
+ final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+ assertTrue("When timeout happens, IC#getSurroundingText() returns null",
+ result.isNullReturnValue());
+ methodCallVerifier.assertCalledOnce(args -> {
+ assertEquals(expectedBeforeLength, args.get("beforeLength"));
+ assertEquals(expectedAfterLength, args.get("afterLength"));
+ assertEquals(expectedFlags, args.get("flags"));
+ });
+ }, blocker);
+ }
+
+ /**
+ * Test {@link InputConnection#getSurroundingText(int, int, int)} fail-fasts once unbindInput()
+ * is issued.
+ */
+ @Test
+ public void testGetSurroundingTextFailFastAfterUnbindInput() throws Exception {
+ final int beforeLength = 3;
+ final int afterLength = 4;
+ final int flags = InputConnection.GET_TEXT_WITH_STYLES;
+ final SurroundingText unexpectedResult = new SurroundingText("012345", 1, 2, 0);
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public SurroundingText getSurroundingText(int beforeLength, int afterLength,
+ int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("beforeLength", beforeLength);
+ args.putInt("afterLength", afterLength);
+ args.putInt("flags", flags);
+ });
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now IC#getTextBeforeCursor() for the memorized IC should fail fast.
+ final ImeEvent result = expectCommand(stream, session.callGetSurroundingText(
+ beforeLength, afterLength, flags), TIMEOUT);
+ assertTrue("Once unbindInput() happened, IC#getSurroundingText() returns null",
+ result.isNullReturnValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+ methodCallVerifier.assertNotCalled(
+ "Once unbindInput() happened, IC#getSurroundingText() fails fast.");
+ });
+ }
+
+ /**
+ * Verify that the default implementation of
+ * {@link InputConnection#getSurroundingText(int, int, int)} returns {@code null} without any
+ * crash even when the target app does not override it .
+ */
+ @Test
+ public void testGetSurroundingTextDefaultMethod() throws Exception {
+ testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetSurroundingText(1, 2, 0);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ assertTrue("Default IC#getSurroundingText() returns null.",
+ result.isNullReturnValue());
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getCursorCapsMode(int)} works as expected.
+ */
+ @Test
+ public void testGetCursorCapsMode() throws Exception {
+ final int expectedReqMode = TextUtils.CAP_MODE_SENTENCES | TextUtils.CAP_MODE_CHARACTERS
+ | TextUtils.CAP_MODE_WORDS;
+ final int expectedResult = EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public int getCursorCapsMode(int reqModes) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("reqModes", reqModes);
+ });
+ return expectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetCursorCapsMode(expectedReqMode);
+ final int result = expectCommand(stream, command, TIMEOUT).getReturnIntegerValue();
+ assertEquals(expectedResult, result);
+ methodCallVerifier.assertCalledOnce(args -> {
+ assertEquals(expectedReqMode, args.getInt("reqModes"));
+ });
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getCursorCapsMode(int)} fails after a system-defined time-out
+ * even if the target app does not respond.
+ */
+ @Test
+ public void testGetCursorCapsModeFailWithTimeout() throws Exception {
+ final int expectedReqMode = TextUtils.CAP_MODE_SENTENCES | TextUtils.CAP_MODE_CHARACTERS
+ | TextUtils.CAP_MODE_WORDS;
+ final int unexpectedResult = EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
+ final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public int getCursorCapsMode(int reqModes) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("reqModes", reqModes);
+ });
+ blocker.onMethodCalled();
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetCursorCapsMode(expectedReqMode);
+ blocker.expectMethodCalled("IC#getCursorCapsMode() must be called back", TIMEOUT);
+ final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+ assertEquals("When timeout happens, IC#getCursorCapsMode() returns 0",
+ 0, result.getReturnIntegerValue());
+ methodCallVerifier.assertCalledOnce(args -> {
+ assertEquals(expectedReqMode, args.getInt("reqModes"));
+ });
+ }, blocker);
+ }
+
+ /**
+ * Test {@link InputConnection#getCursorCapsMode(int)} fail-fasts once unbindInput() is issued.
+ */
+ @Test
+ public void testGetCursorCapsModeFailFastAfterUnbindInput() throws Exception {
+ final int unexpectedResult = EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public int getCursorCapsMode(int reqModes) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("reqModes", reqModes);
+ });
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now IC#getCursorCapsMode() for the memorized IC should fail fast.
+ final ImeEvent result = expectCommand(stream,
+ session.callGetCursorCapsMode(TextUtils.CAP_MODE_WORDS), TIMEOUT);
+ assertEquals("Once unbindInput() happened, IC#getCursorCapsMode() returns 0",
+ 0, result.getReturnIntegerValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+ methodCallVerifier.assertNotCalled(
+ "Once unbindInput() happened, IC#getCursorCapsMode() fails fast.");
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getExtractedText(ExtractedTextRequest, int)} works as expected.
+ */
+ @Test
+ public void testGetExtractedText() throws Exception {
+ final ExtractedTextRequest expectedRequest = ExtractedTextRequestTest.createForTest();
+ final int expectedFlags = InputConnection.GET_EXTRACTED_TEXT_MONITOR;
+ final ExtractedText expectedResult = ExtractedTextTest.createForTest();
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putParcelable("request", request);
+ args.putInt("flags", flags);
+ });
+ return expectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetExtractedText(expectedRequest, expectedFlags);
+ final ExtractedText result =
+ expectCommand(stream, command, TIMEOUT).getReturnParcelableValue();
+ ExtractedTextTest.assertTestInstance(result);
+ methodCallVerifier.assertCalledOnce(args -> {
+ ExtractedTextRequestTest.assertTestInstance(args.getParcelable("request"));
+ assertEquals(expectedFlags, args.getInt("flags"));
+ });
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getExtractedText(ExtractedTextRequest, int)} fails after a
+ * system-defined time-out even if the target app does not respond.
+ */
+ @Test
+ public void testGetExtractedTextFailWithTimeout() throws Exception {
+ final ExtractedTextRequest expectedRequest = ExtractedTextRequestTest.createForTest();
+ final int expectedFlags = InputConnection.GET_EXTRACTED_TEXT_MONITOR;
+ final ExtractedText unexpectedResult = ExtractedTextTest.createForTest();
+ final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putParcelable("request", request);
+ args.putInt("flags", flags);
+ });
+ blocker.onMethodCalled();
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetExtractedText(expectedRequest, expectedFlags);
+ blocker.expectMethodCalled("IC#getExtractedText() must be called back", TIMEOUT);
+ final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+ assertTrue("When timeout happens, IC#getExtractedText() returns null",
+ result.isNullReturnValue());
+ methodCallVerifier.assertCalledOnce(args -> {
+ ExtractedTextRequestTest.assertTestInstance(args.getParcelable("request"));
+ assertEquals(expectedFlags, args.getInt("flags"));
+ });
+ }, blocker);
+ }
+
+ /**
+ * Test {@link InputConnection#getExtractedText(ExtractedTextRequest, int)} fail-fasts once
+ * unbindInput() is issued.
+ */
+ @Test
+ public void testGetExtractedTextFailFastAfterUnbindInput() throws Exception {
+ final ExtractedText unexpectedResult = ExtractedTextTest.createForTest();
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putParcelable("request", request);
+ args.putInt("flags", flags);
+ });
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now IC#getExtractedText() for the memorized IC should fail fast.
+ final ImeEvent result = expectCommand(stream, session.callGetExtractedText(
+ ExtractedTextRequestTest.createForTest(),
+ InputConnection.GET_EXTRACTED_TEXT_MONITOR), TIMEOUT);
+ assertTrue("Once unbindInput() happened, IC#getExtractedText() returns null",
+ result.isNullReturnValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+ methodCallVerifier.assertNotCalled(
+ "Once unbindInput() happened, IC#getExtractedText() fails fast.");
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#requestCursorUpdates(int)} works as expected.
+ */
+ @Test
+ public void testRequestCursorUpdates() throws Exception {
+ final int expectedFlags = InputConnection.CURSOR_UPDATE_IMMEDIATE;
+ final boolean expectedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean requestCursorUpdates(int cursorUpdateMode) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("cursorUpdateMode", cursorUpdateMode);
+ });
+ assertEquals(expectedFlags, cursorUpdateMode);
+ return expectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callRequestCursorUpdates(expectedFlags);
+ assertTrue(expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.assertCalledOnce(args -> {
+ assertEquals(expectedFlags, args.getInt("cursorUpdateMode"));
+ });
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#requestCursorUpdates(int)} fails after a system-defined time-out
+ * even if the target app does not respond.
+ */
+ @Test
+ public void testRequestCursorUpdatesFailWithTimeout() throws Exception {
+ final int expectedFlags = InputConnection.CURSOR_UPDATE_IMMEDIATE;
+ final boolean unexpectedResult = true;
+ final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean requestCursorUpdates(int cursorUpdateMode) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("cursorUpdateMode", cursorUpdateMode);
+ });
+ blocker.onMethodCalled();
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callRequestCursorUpdates(
+ InputConnection.CURSOR_UPDATE_IMMEDIATE);
+ blocker.expectMethodCalled("IC#requestCursorUpdates() must be called back", TIMEOUT);
+ final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+ assertFalse("When timeout happens, IC#requestCursorUpdates() returns false",
+ result.getReturnBooleanValue());
+ methodCallVerifier.assertCalledOnce(args -> {
+ assertEquals(expectedFlags, args.getInt("cursorUpdateMode"));
+ });
+ }, blocker);
+ }
+
+ /**
+ * Test {@link InputConnection#requestCursorUpdates(int)} fail-fasts once unbindInput() is
+ * issued.
+ */
+ @Test
+ public void testRequestCursorUpdatesFailFastAfterUnbindInput() throws Exception {
+ final boolean unexpectedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean requestCursorUpdates(int cursorUpdateMode) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("cursorUpdateMode", cursorUpdateMode);
+ });
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now IC#requestCursorUpdates() for the memorized IC should fail fast.
+ final ImeEvent result = expectCommand(stream, session.callRequestCursorUpdates(
+ InputConnection.CURSOR_UPDATE_IMMEDIATE), TIMEOUT);
+ assertFalse("Once unbindInput() happened, IC#requestCursorUpdates() returns false",
+ result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+ methodCallVerifier.assertNotCalled(
+ "Once unbindInput() happened, IC#requestCursorUpdates() fails fast.");
+ });
+ }
+
+ /**
+ * Verify that {@link InputConnection#requestCursorUpdates(int)} fails when the target app does
+ * not implement it. This can happen if the app was built before
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
+ */
+ @Test
+ public void testRequestCursorUpdatesFailWithMethodMissing() throws Exception {
+ testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callRequestCursorUpdates(
+ InputConnection.CURSOR_UPDATE_IMMEDIATE);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ assertFalse("IC#requestCursorUpdates() returns false when the target app does not "
+ + " implement it.", result.getReturnBooleanValue());
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} works as expected.
+ */
+ @Test
+ public void testCommitContent() throws Exception {
+ final InputContentInfo expectedInputContentInfo = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+ final Bundle expectedOpt = new Bundle();
+ final String expectedOptKey = "testKey";
+ final int expectedOptValue = 42;
+ expectedOpt.putInt(expectedOptKey, expectedOptValue);
+ final int expectedFlags = InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
+ final boolean expectedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean commitContent(InputContentInfo inputContentInfo, int flags,
+ Bundle opts) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putParcelable("inputContentInfo", inputContentInfo);
+ args.putInt("flags", flags);
+ args.putBundle("opts", opts);
+ });
+ return expectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command =
+ session.callCommitContent(expectedInputContentInfo, expectedFlags, expectedOpt);
+ assertTrue(expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.assertCalledOnce(args -> {
+ final InputContentInfo inputContentInfo = args.getParcelable("inputContentInfo");
+ final Bundle opts = args.getBundle("opts");
+ assertNotNull(inputContentInfo);
+ assertEquals(expectedInputContentInfo.getContentUri(),
+ inputContentInfo.getContentUri());
+ assertEquals(expectedFlags, args.getInt("flags"));
+ assertNotNull(opts);
+ assertEquals(expectedOpt.getInt(expectedOptKey), opts.getInt(expectedOptKey));
+ });
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} fails after a
+ * system-defined time-out even if the target app does not respond.
+ */
+ @Test
+ public void testCommitContentFailWithTimeout() throws Exception {
+ final InputContentInfo expectedInputContentInfo = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+ final Bundle expectedOpt = new Bundle();
+ final String expectedOptKey = "testKey";
+ final int expectedOptValue = 42;
+ expectedOpt.putInt(expectedOptKey, expectedOptValue);
+ final int expectedFlags = InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
+ final boolean unexpectedResult = true;
+ final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean commitContent(InputContentInfo inputContentInfo, int flags,
+ Bundle opts) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putParcelable("inputContentInfo", inputContentInfo);
+ args.putInt("flags", flags);
+ args.putBundle("opts", opts);
+ });
+ blocker.onMethodCalled();
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command =
+ session.callCommitContent(expectedInputContentInfo, expectedFlags, expectedOpt);
+ blocker.expectMethodCalled("IC#commitContent() must be called back", TIMEOUT);
+ final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+ assertFalse("When timeout happens, IC#commitContent() returns false",
+ result.getReturnBooleanValue());
+ methodCallVerifier.assertCalledOnce(args -> {
+ final InputContentInfo inputContentInfo = args.getParcelable("inputContentInfo");
+ final Bundle opts = args.getBundle("opts");
+ assertNotNull(inputContentInfo);
+ assertEquals(expectedInputContentInfo.getContentUri(),
+ inputContentInfo.getContentUri());
+ assertEquals(expectedFlags, args.getInt("flags"));
+ assertNotNull(opts);
+ assertEquals(expectedOpt.getInt(expectedOptKey), opts.getInt(expectedOptKey));
+ });
+ }, blocker);
+ }
+
+ /**
+ * Test {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} fail-fasts once
+ * unbindInput() is issued.
+ */
+ @Test
+ public void testCommitContentFailFastAfterUnbindInput() throws Exception {
+ final boolean unexpectedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean commitContent(InputContentInfo inputContentInfo, int flags,
+ Bundle opts) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putParcelable("inputContentInfo", inputContentInfo);
+ args.putInt("flags", flags);
+ args.putBundle("opts", opts);
+ });
+ return unexpectedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now IC#getTextAfterCursor() for the memorized IC should fail fast.
+ final ImeEvent result = expectCommand(stream, session.callCommitContent(
+ new InputContentInfo(Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com")), 0, null), TIMEOUT);
+ assertFalse("Once unbindInput() happened, IC#commitContent() returns false",
+ result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+ methodCallVerifier.assertNotCalled(
+ "Once unbindInput() happened, IC#commitContent() fails fast.");
+ });
+ }
+
+ /**
+ * Verify that {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} fails when
+ * the target app does not implement it. This can happen if the app was built before
+ * {@link android.os.Build.VERSION_CODES#N_MR1}.
+ */
+ @Test
+ public void testCommitContentFailWithMethodMissing() throws Exception {
+ testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callCommitContent(
+ new InputContentInfo(Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com")), 0, null);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertFalse("Currently IC#commitContent() returns false when the target app does not"
+ + " implement it.", result.getReturnBooleanValue());
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#deleteSurroundingText(int, int)} works as expected.
+ */
+ @Test
+ public void testDeleteSurroundingText() throws Exception {
+ final int expectedBeforeLength = 5;
+ final int expectedAfterLength = 4;
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("beforeLength", beforeLength);
+ args.putInt("afterLength", afterLength);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command =
+ session.callDeleteSurroundingText(expectedBeforeLength, expectedAfterLength);
+ assertTrue("deleteSurroundingText() always returns true unless RemoteException is"
+ + " thrown", expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ assertEquals(expectedBeforeLength, args.getInt("beforeLength"));
+ assertEquals(expectedAfterLength, args.getInt("afterLength"));
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#deleteSurroundingText(int, int)} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testDeleteSurroundingTextAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("beforeLength", beforeLength);
+ args.putInt("afterLength", afterLength);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now IC#deleteSurroundingText() for the memorized IC should fail fast.
+ final ImeCommand command = session.callDeleteSurroundingText(3, 4);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#deleteSurroundingText() still returns true even after"
+ + " unbindInput().", result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#deleteSurroundingText() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)} works as expected.
+ */
+ @Test
+ public void testDeleteSurroundingTextInCodePoints() throws Exception {
+ final int expectedBeforeLength = 5;
+ final int expectedAfterLength = 4;
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("beforeLength", beforeLength);
+ args.putInt("afterLength", afterLength);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callDeleteSurroundingTextInCodePoints(
+ expectedBeforeLength, expectedAfterLength);
+ assertTrue("deleteSurroundingText() always returns true unless RemoteException is"
+ + " thrown", expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ assertEquals(expectedBeforeLength, args.getInt("beforeLength"));
+ assertEquals(expectedAfterLength, args.getInt("afterLength"));
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testDeleteSurroundingTextInCodePointsAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("beforeLength", beforeLength);
+ args.putInt("afterLength", afterLength);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now IC#deleteSurroundingTextInCodePoints() for the memorized IC should fail fast.
+ final ImeCommand command = session.callDeleteSurroundingTextInCodePoints(3, 4);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#deleteSurroundingTextInCodePoints() still returns true even"
+ + " after unbindInput().", result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#deleteSurroundingTextInCodePoints() fails"
+ + " fast.", EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Verify that the app does not crash even if it does not implement
+ * {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)}, which can happen if the
+ * app was built before {@link android.os.Build.VERSION_CODES#N}.
+ */
+ @Test
+ public void testDeleteSurroundingTextInCodePointsFailWithMethodMissing() throws Exception {
+ testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callDeleteSurroundingTextInCodePoints(1, 2);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ assertTrue("IC#deleteSurroundingTextInCodePoints() returns true even when the target"
+ + " app does not implement it.", result.getReturnBooleanValue());
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#commitText(CharSequence, int)} works as expected.
+ */
+ @Test
+ public void testCommitText() throws Exception {
+ final Annotation expectedSpan = new Annotation("expectedKey", "expectedValue");
+ final CharSequence expectedText = createTestCharSequence("expectedText", expectedSpan);
+ final int expectedNewCursorPosition = 123;
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putCharSequence("text", text);
+ args.putInt("newCursorPosition", newCursorPosition);
+ });
+
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command =
+ session.callCommitText(expectedText, expectedNewCursorPosition);
+ assertTrue("commitText() always returns true unless RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ assertEqualsForTestCharSequence(expectedText, args.getCharSequence("text"));
+ assertEquals(expectedNewCursorPosition, args.getInt("newCursorPosition"));
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#commitText(CharSequence, int)} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testCommitTextAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putCharSequence("text", text);
+ args.putInt("newCursorPosition", newCursorPosition);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now IC#getTextAfterCursor() for the memorized IC should fail fast.
+ final ImeEvent result = expectCommand(stream,
+ session.callCommitText("text", 1), TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#commitText() still returns true even after unbindInput().",
+ result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#commitText() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#commitText(CharSequence, int, TextAttribute)} works as expected.
+ */
+ @Test
+ public void testCommitTextWithTextAttribute() throws Exception {
+ final Annotation expectedSpan = new Annotation("expectedKey", "expectedValue");
+ final CharSequence expectedText = createTestCharSequence("expectedText", expectedSpan);
+ final int expectedNewCursorPosition = 123;
+ final ArrayList<String> expectedSuggestions = new ArrayList<>();
+ expectedSuggestions.add("test");
+ final TextAttribute expectedTextAttribute = new TextAttribute.Builder()
+ .setTextConversionSuggestions(expectedSuggestions).build();
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean commitText(
+ CharSequence text, int newCursorPosition, TextAttribute textAttribute) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putCharSequence("text", text);
+ args.putInt("newCursorPosition", newCursorPosition);
+ args.putParcelable("textAttribute", textAttribute);
+ });
+
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callCommitText(
+ expectedText, expectedNewCursorPosition, expectedTextAttribute);
+ assertTrue("commitText() always returns true unless RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ assertEqualsForTestCharSequence(expectedText, args.getCharSequence("text"));
+ assertEquals(expectedNewCursorPosition, args.getInt("newCursorPosition"));
+ final TextAttribute textAttribute = args.getParcelable("textAttribute");
+ assertThat(textAttribute).isNotNull();
+ assertThat(textAttribute.getTextConversionSuggestions())
+ .containsExactlyElementsIn(expectedSuggestions);
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#commitText(CharSequence, int, TextAttribute)} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testCommitTextAfterUnbindInputWithTextAttribute() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean commitText(
+ CharSequence text, int newCursorPosition, TextAttribute textAttribute) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putCharSequence("text", text);
+ args.putInt("newCursorPosition", newCursorPosition);
+ args.putParcelable("textAttribute", textAttribute);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now IC#getTextAfterCursor() for the memorized IC should fail fast.
+ final ImeEvent result = expectCommand(stream,
+ session.callCommitText("text", 1,
+ new TextAttribute.Builder().setTextConversionSuggestions(
+ Collections.singletonList("test")).build()),
+ TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#commitText() still returns true even after unbindInput().",
+ result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#commitText() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#setComposingText(CharSequence, int)} works as expected.
+ */
+ @Test
+ public void testSetComposingText() throws Exception {
+ final Annotation expectedSpan = new Annotation("expectedKey", "expectedValue");
+ final CharSequence expectedText = createTestCharSequence("expectedText", expectedSpan);
+ final int expectedNewCursorPosition = 123;
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putCharSequence("text", text);
+ args.putInt("newCursorPosition", newCursorPosition);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command =
+ session.callSetComposingText(expectedText, expectedNewCursorPosition);
+ assertTrue("setComposingText() always returns true unless RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ assertEqualsForTestCharSequence(expectedText, args.getCharSequence("text"));
+ assertEquals(expectedNewCursorPosition, args.getInt("newCursorPosition"));
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#setComposingText(CharSequence, int)} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testSetComposingTextAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putCharSequence("text", text);
+ args.putInt("newCursorPosition", newCursorPosition);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callSetComposingText("text", 1);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#setComposingText() still returns true even after "
+ + "unbindInput().", result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#setComposingText() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#setComposingText(CharSequence, int, TextAttribute)}
+ * works as expected.
+ */
+ @Test
+ public void testSetComposingTextWithTextAttribute() throws Exception {
+ final Annotation expectedSpan = new Annotation("expectedKey", "expectedValue");
+ final CharSequence expectedText = createTestCharSequence("expectedText", expectedSpan);
+ final int expectedNewCursorPosition = 123;
+ final ArrayList<String> expectedSuggestions = new ArrayList<>();
+ expectedSuggestions.add("test");
+ final TextAttribute expectedTextAttribute = new TextAttribute.Builder()
+ .setTextConversionSuggestions(expectedSuggestions).build();
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean setComposingText(CharSequence text, int newCursorPosition,
+ TextAttribute textAttribute) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putCharSequence("text", text);
+ args.putInt("newCursorPosition", newCursorPosition);
+ args.putParcelable("textAttribute", textAttribute);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callSetComposingText(
+ expectedText, expectedNewCursorPosition, expectedTextAttribute);
+ assertTrue("testSetComposingTextWithTextAttribute() always returns true unless"
+ + " RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ assertEqualsForTestCharSequence(expectedText, args.getCharSequence("text"));
+ assertEquals(expectedNewCursorPosition, args.getInt("newCursorPosition"));
+ final TextAttribute textAttribute = args.getParcelable("textAttribute");
+ assertThat(textAttribute).isNotNull();
+ assertThat(textAttribute.getTextConversionSuggestions())
+ .containsExactlyElementsIn(expectedSuggestions);
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#setComposingText(CharSequence, int, TextAttribute)} fails fast
+ * once {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testSetComposingTextAfterUnbindInputWithTextAttribute() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean setComposingText(CharSequence text, int newCursorPosition,
+ TextAttribute textAttribute) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putCharSequence("text", text);
+ args.putInt("newCursorPosition", newCursorPosition);
+ args.putParcelable("textAttribute", textAttribute);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callSetComposingText(
+ "text", 1, new TextAttribute.Builder()
+ .setTextConversionSuggestions(Collections.singletonList("test"))
+ .build());
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#setComposingText() still returns true even after "
+ + "unbindInput().", result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#setComposingText() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#setComposingRegion(int, int)} works as expected.
+ */
+ @Test
+ public void testSetComposingRegion() throws Exception {
+ final int expectedStart = 3;
+ final int expectedEnd = 17;
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean setComposingRegion(int start, int end) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("start", start);
+ args.putInt("end", end);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callSetComposingRegion(expectedStart, expectedEnd);
+ assertTrue("setComposingRegion() always returns true unless RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ assertEquals(expectedStart, args.getInt("start"));
+ assertEquals(expectedEnd, args.getInt("end"));
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#setComposingRegion(int, int)} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testSetComposingRegionTextAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean setComposingRegion(int start, int end) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("start", start);
+ args.putInt("end", end);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callSetComposingRegion(1, 23);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#setComposingRegion() still returns true even after"
+ + " unbindInput().", result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#setComposingRegion() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Verify that the app does not crash even if it does not implement
+ * {@link InputConnection#setComposingRegion(int, int)}, which can happen if the app was built
+ * before {@link android.os.Build.VERSION_CODES#GINGERBREAD}.
+ */
+ @Test
+ public void testSetComposingRegionFailWithMethodMissing() throws Exception {
+ testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callSetComposingRegion(1, 23);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ assertTrue("IC#setComposingRegion() returns true even when the target app does not"
+ + " implement it.", result.getReturnBooleanValue());
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#setComposingRegion} works as expected.
+ */
+ @Test
+ public void testSetComposingRegionWithTextAttribute() throws Exception {
+ final int expectedStart = 3;
+ final int expectedEnd = 17;
+ final ArrayList<String> expectedSuggestions = new ArrayList<>();
+ expectedSuggestions.add("test");
+ final TextAttribute expectedTextAttribute = new TextAttribute.Builder()
+ .setTextConversionSuggestions(expectedSuggestions).build();
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean setComposingRegion(
+ int start, int end, TextAttribute textAttribute) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("start", start);
+ args.putInt("end", end);
+ args.putParcelable("textAttribute", textAttribute);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callSetComposingRegion(
+ expectedStart, expectedEnd, expectedTextAttribute);
+ assertTrue("setComposingRegion() always returns true unless RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ assertEquals(expectedStart, args.getInt("start"));
+ assertEquals(expectedEnd, args.getInt("end"));
+ final TextAttribute textAttribute = args.getParcelable("textAttribute");
+ assertThat(textAttribute).isNotNull();
+ assertThat(textAttribute.getTextConversionSuggestions())
+ .containsExactlyElementsIn(expectedSuggestions);
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#setComposingRegion(int, int, TextAttribute)} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testSetComposingRegionTextAfterUnbindInputWithTextAttribute() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean setComposingRegion(int start, int end, TextAttribute textAttribute) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("start", start);
+ args.putInt("end", end);
+ args.putParcelable("textAttribute", textAttribute);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callSetComposingRegion(1, 23,
+ new TextAttribute.Builder().setTextConversionSuggestions(
+ Collections.singletonList("test")).build());
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#setComposingRegion() still returns true even after"
+ + " unbindInput().", result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#setComposingRegion() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#finishComposingText()} works as expected.
+ */
+ @Test
+ public void testFinishComposingText() throws Exception {
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean finishComposingText() {
+ methodCallVerifier.onMethodCalled(bundle -> { });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callFinishComposingText();
+ assertTrue("finishComposingText() always returns true unless RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> { }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#finishComposingText()} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testFinishComposingTextAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean finishComposingText() {
+ methodCallVerifier.onMethodCalled(bundle -> { });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // The system internally calls "finishComposingText". So wait for a while then reset
+ // the verifier before our calling "finishComposingText".
+ SystemClock.sleep(TIMEOUT);
+ methodCallVerifier.reset();
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callFinishComposingText();
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#finishComposingText() still returns true even after"
+ + " unbindInput().", result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#finishComposingText() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#commitCompletion(CompletionInfo)} works as expected.
+ */
+ @Test
+ public void testCommitCompletion() throws Exception {
+ final CompletionInfo expectedCompletionInfo = new CompletionInfo(0x12345678, 0x87654321,
+ createTestCharSequence("testText", new Annotation("param", "text")),
+ createTestCharSequence("testLabel", new Annotation("param", "label")));
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean commitCompletion(CompletionInfo text) {
+ methodCallVerifier.onMethodCalled(bundle -> {
+ bundle.putParcelable("text", text);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callCommitCompletion(expectedCompletionInfo);
+ assertTrue("commitCompletion() always returns true unless RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ final CompletionInfo actualCompletionInfo = args.getParcelable("text");
+ assertNotNull(actualCompletionInfo);
+ assertEquals(expectedCompletionInfo.getId(), actualCompletionInfo.getId());
+ assertEquals(expectedCompletionInfo.getPosition(),
+ actualCompletionInfo.getPosition());
+ assertEqualsForTestCharSequence(expectedCompletionInfo.getText(),
+ actualCompletionInfo.getText());
+ assertEqualsForTestCharSequence(expectedCompletionInfo.getLabel(),
+ actualCompletionInfo.getLabel());
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#commitCompletion(CompletionInfo)} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testCommitCompletionAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean commitCompletion(CompletionInfo text) {
+ methodCallVerifier.onMethodCalled(bundle -> {
+ bundle.putParcelable("text", text);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callCommitCompletion(new CompletionInfo(
+ 0x12345678, 0x87654321,
+ createTestCharSequence("testText", new Annotation("param", "text")),
+ createTestCharSequence("testLabel", new Annotation("param", "label"))));
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#commitCompletion() still returns true even after"
+ + " unbindInput().", result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#commitCompletion() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#commitCorrection(CorrectionInfo)} works as expected.
+ */
+ @Test
+ public void testCommitCorrection() throws Exception {
+ final CorrectionInfo expectedCorrectionInfo = new CorrectionInfo(0x11111111,
+ createTestCharSequence("testOldText", new Annotation("param", "oldText")),
+ createTestCharSequence("testNewText", new Annotation("param", "newText")));
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean commitCorrection(CorrectionInfo correctionInfo) {
+ methodCallVerifier.onMethodCalled(bundle -> {
+ bundle.putParcelable("correctionInfo", correctionInfo);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callCommitCorrection(expectedCorrectionInfo);
+ assertTrue("commitCorrection() always returns true unless RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ final CorrectionInfo actualCorrectionInfo = args.getParcelable("correctionInfo");
+ assertNotNull(actualCorrectionInfo);
+ assertEquals(expectedCorrectionInfo.getOffset(),
+ actualCorrectionInfo.getOffset());
+ assertEqualsForTestCharSequence(expectedCorrectionInfo.getOldText(),
+ actualCorrectionInfo.getOldText());
+ assertEqualsForTestCharSequence(expectedCorrectionInfo.getNewText(),
+ actualCorrectionInfo.getNewText());
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#commitCorrection(CorrectionInfo)} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testCommitCorrectionAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean commitCorrection(CorrectionInfo correctionInfo) {
+ methodCallVerifier.onMethodCalled(bundle -> {
+ bundle.putParcelable("correctionInfo", correctionInfo);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callCommitCorrection(new CorrectionInfo(0x11111111,
+ createTestCharSequence("testOldText", new Annotation("param", "oldText")),
+ createTestCharSequence("testNewText", new Annotation("param", "newText"))));
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#commitCorrection() still returns true even after"
+ + " unbindInput().", result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#commitCorrection() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Verify that the app does not crash even if it does not implement
+ * {@link InputConnection#commitCorrection(CorrectionInfo)}, which can happen if the app was
+ * built before {@link android.os.Build.VERSION_CODES#HONEYCOMB}.
+ */
+ @Test
+ public void testCommitCorrectionFailWithMethodMissing() throws Exception {
+ testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callCommitCorrection(new CorrectionInfo(0x11111111,
+ createTestCharSequence("testOldText", new Annotation("param", "oldText")),
+ createTestCharSequence("testNewText", new Annotation("param", "newText"))));
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ assertTrue("IC#commitCorrection() returns true even when the target app does not"
+ + " implement it.", result.getReturnBooleanValue());
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#setSelection(int, int)} works as expected.
+ */
+ @Test
+ public void testSetSelection() throws Exception {
+ final int expectedStart = 123;
+ final int expectedEnd = 456;
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean setSelection(int start, int end) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("start", start);
+ args.putInt("end", end);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callSetSelection(expectedStart, expectedEnd);
+ assertTrue("setSelection() always returns true unless RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ assertEquals(expectedStart, args.getInt("start"));
+ assertEquals(expectedEnd, args.getInt("end"));
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#setSelection(int, int)} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testSetSelectionTextAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean setSelection(int start, int end) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("start", start);
+ args.putInt("end", end);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callSetSelection(123, 456);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#setSelection() still returns true even after unbindInput().",
+ result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#setSelection() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#performEditorAction(int)} works as expected.
+ */
+ @Test
+ public void testPerformEditorAction() throws Exception {
+ final int expectedEditorAction = EditorInfo.IME_ACTION_GO;
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean performEditorAction(int editorAction) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("editorAction", editorAction);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callPerformEditorAction(expectedEditorAction);
+ assertTrue("performEditorAction() always returns true unless RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ assertEquals(expectedEditorAction, args.getInt("editorAction"));
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#performEditorAction(int)} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testPerformEditorActionAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean performEditorAction(int editorAction) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("editorAction", editorAction);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callPerformEditorAction(EditorInfo.IME_ACTION_GO);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#performEditorAction() still returns true even after "
+ + "unbindInput().", result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#performEditorAction() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#performContextMenuAction(int)} works as expected.
+ */
+ @Test
+ public void testPerformContextMenuAction() throws Exception {
+ final int expectedId = android.R.id.selectAll;
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean performContextMenuAction(int id) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("id", id);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callPerformContextMenuAction(expectedId);
+ assertTrue("performContextMenuAction() always returns true unless RemoteException is "
+ + "thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ assertEquals(expectedId, args.getInt("id"));
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#performContextMenuAction(int)} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testPerformContextMenuActionAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean performContextMenuAction(int id) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("id", id);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callPerformEditorAction(EditorInfo.IME_ACTION_GO);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#performContextMenuAction() still returns true even after "
+ + "unbindInput().", result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#performContextMenuAction() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#beginBatchEdit()} works as expected.
+ */
+ @Test
+ public void testBeginBatchEdit() throws Exception {
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean beginBatchEdit() {
+ methodCallVerifier.onMethodCalled(args -> { });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callBeginBatchEdit();
+ assertTrue("beginBatchEdit() always returns true unless RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> { }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#beginBatchEdit()} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testBeginBatchEditAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean beginBatchEdit() {
+ methodCallVerifier.onMethodCalled(args -> { });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callBeginBatchEdit();
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#beginBatchEdit() still returns true even after unbindInput().",
+ result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#beginBatchEdit() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#endBatchEdit()} works as expected.
+ */
+ @Test
+ public void testEndBatchEdit() throws Exception {
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean endBatchEdit() {
+ methodCallVerifier.onMethodCalled(args -> { });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callEndBatchEdit();
+ assertTrue("endBatchEdit() always returns true unless RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> { }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#endBatchEdit()} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testEndBatchEditAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean endBatchEdit() {
+ methodCallVerifier.onMethodCalled(args -> { });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callEndBatchEdit();
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#endBatchEdit() still returns true even after unbindInput().",
+ result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#endBatchEdit() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#sendKeyEvent(KeyEvent)} works as expected.
+ */
+ @Test
+ public void testSendKeyEvent() throws Exception {
+ final KeyEvent expectedKeyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_X);
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean sendKeyEvent(KeyEvent event) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putParcelable("event", event);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callSendKeyEvent(expectedKeyEvent);
+ assertTrue("sendKeyEvent() always returns true unless RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ final KeyEvent actualKeyEvent = args.getParcelable("event");
+ assertNotNull(actualKeyEvent);
+ assertEquals(expectedKeyEvent.getAction(), actualKeyEvent.getAction());
+ assertEquals(expectedKeyEvent.getKeyCode(), actualKeyEvent.getKeyCode());
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#sendKeyEvent(KeyEvent)} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testSendKeyEventAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean sendKeyEvent(KeyEvent event) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putParcelable("event", event);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callSendKeyEvent(
+ new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_X));
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#sendKeyEvent() still returns true even after unbindInput().",
+ result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#sendKeyEvent() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#clearMetaKeyStates(int)} works as expected.
+ */
+ @Test
+ public void testClearMetaKeyStates() throws Exception {
+ final int expectedStates = KeyEvent.META_ALT_MASK;
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean clearMetaKeyStates(int states) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("states", states);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callClearMetaKeyStates(expectedStates);
+ assertTrue("clearMetaKeyStates() always returns true unless RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ final int actualStates = args.getInt("states");
+ assertEquals(expectedStates, actualStates);
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#clearMetaKeyStates(int)} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testClearMetaKeyStatesAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean clearMetaKeyStates(int states) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putInt("states", states);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callClearMetaKeyStates(KeyEvent.META_ALT_MASK);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#clearMetaKeyStates() still returns true even after "
+ + "unbindInput().", result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#clearMetaKeyStates() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#reportFullscreenMode(boolean)} is ignored as expected.
+ */
+ @Test
+ public void testReportFullscreenMode() throws Exception {
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean reportFullscreenMode(boolean enabled) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putBoolean("enabled", enabled);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callReportFullscreenMode(true);
+ assertFalse("reportFullscreenMode() always returns false on API 26+",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "IC#reportFullscreenMode() must be ignored on API 26+",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#reportFullscreenMode(boolean)} is ignored as expected even after
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testReportFullscreenModeAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean reportFullscreenMode(boolean enabled) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putBoolean("enabled", enabled);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callReportFullscreenMode(true);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ assertFalse("reportFullscreenMode() always returns false on API 26+",
+ result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled("IC#reportFullscreenMode() must be ignored on "
+ + "API 26+ even after unbindInput().", EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#performSpellCheck()} works as expected.
+ */
+ @Test
+ public void testPerformSpellCheck() throws Exception {
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean performSpellCheck() {
+ methodCallVerifier.onMethodCalled(args -> { });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callPerformSpellCheck();
+ assertTrue("performSpellCheck() always returns true unless RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> { }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#performSpellCheck()} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testPerformSpellCheckAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean performSpellCheck() {
+ methodCallVerifier.onMethodCalled(args -> { });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callPerformSpellCheck();
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#performSpellCheck() still returns true even after "
+ + "unbindInput().", result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#performSpellCheck() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Verify that the default implementation of {@link InputConnection#performSpellCheck()}
+ * returns {@code true} without any crash even when the target app does not override it.
+ */
+ @Test
+ public void testPerformSpellCheckDefaultMethod() throws Exception {
+ testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callPerformSpellCheck();
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ assertTrue("IC#performSpellCheck() still returns true even when the target "
+ + "application does not implement it.", result.getReturnBooleanValue());
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#performPrivateCommand(String, Bundle)} works as expected.
+ */
+ @Test
+ public void testPerformPrivateCommand() throws Exception {
+ final String expectedAction = "myAction";
+ final Bundle expectedData = new Bundle();
+ final String expectedDataKey = "testKey";
+ final int expectedDataValue = 42;
+ expectedData.putInt(expectedDataKey, expectedDataValue);
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean performPrivateCommand(String action, Bundle data) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putString("action", action);
+ args.putBundle("data", data);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command =
+ session.callPerformPrivateCommand(expectedAction, expectedData);
+ assertTrue("performPrivateCommand() always returns true unless RemoteException is "
+ + "thrown", expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ final String actualAction = args.getString("action");
+ final Bundle actualData = args.getBundle("data");
+ assertEquals(expectedAction, actualAction);
+ assertNotNull(actualData);
+ assertEquals(expectedData.get(expectedDataKey), actualData.getInt(expectedDataKey));
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#performPrivateCommand(String, Bundle)} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testPerformPrivateCommandAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean performPrivateCommand(String action, Bundle data) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putString("action", action);
+ args.putBundle("data", data);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callPerformPrivateCommand("myAction", null);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#performPrivateCommand() still returns true even after "
+ + "unbindInput().", result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#performPrivateCommand() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getHandler()} is ignored as expected.
+ */
+ @Test
+ public void testGetHandler() throws Exception {
+ final Handler returnedResult = null;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public Handler getHandler() {
+ methodCallVerifier.onMethodCalled(args -> { });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // The system internally calls "getHandler". So reset the verifier before our calling
+ // "callGetHandler".
+ methodCallVerifier.reset();
+ final ImeCommand command = session.callGetHandler();
+ assertTrue("getHandler() always returns null",
+ expectCommand(stream, command, TIMEOUT).isNullReturnValue());
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled("IC#getHandler() must be ignored.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#getHandler()} is ignored as expected even after
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testGetHandlerAfterUnbindInput() throws Exception {
+ final Handler returnedResult = null;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public Handler getHandler() {
+ methodCallVerifier.onMethodCalled(args -> { });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // The system internally calls "getHandler". So reset the verifier before our calling
+ // "callGetHandler".
+ methodCallVerifier.reset();
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callGetHandler();
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ assertTrue("getHandler() always returns null", result.isNullReturnValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "IC#getHandler() must be ignored even after unbindInput().",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Verify that applications that do not implement {@link InputConnection#getHandler()} will not
+ * crash. This can happen if the app was built before {@link android.os.Build.VERSION_CODES#N}.
+ */
+ @Test
+ public void testGetHandlerWithMethodMissing() throws Exception {
+ testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callGetHandler();
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ assertTrue("IC#getHandler() still returns null even when the target app does not"
+ + " implement it.", result.isNullReturnValue());
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#closeConnection()} is ignored as expected.
+ */
+ @Test
+ public void testCloseConnection() throws Exception {
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public void closeConnection() {
+ methodCallVerifier.onMethodCalled(args -> { });
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callCloseConnection();
+ expectCommand(stream, command, TIMEOUT);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled("IC#getHandler() must be ignored.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#closeConnection()} is ignored as expected even after
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testCloseConnectionAfterUnbindInput() throws Exception {
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public void closeConnection() {
+ methodCallVerifier.onMethodCalled(args -> { });
+ latch.countDown();
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // The system internally calls "closeConnection". So wait for it to happen then reset
+ // the verifier before our calling "closeConnection".
+ assertTrue("closeConnection() must be called by the system.",
+ latch.await(TIMEOUT, TimeUnit.MILLISECONDS));
+ methodCallVerifier.reset();
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callCloseConnection();
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "IC#closeConnection() must be ignored even after unbindInput().",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Verify that applications that do not implement {@link InputConnection#closeConnection()}
+ * will not crash. This can happen if the app was built before
+ * {@link android.os.Build.VERSION_CODES#N}.
+ */
+ @Test
+ public void testCloseConnectionWithMethodMissing() throws Exception {
+ testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callCloseConnection();
+ expectCommand(stream, command, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#setImeConsumesInput(boolean)} works as expected.
+ */
+ @Test
+ public void testSetImeConsumesInput() throws Exception {
+ final boolean expectedImeConsumesInput = true;
+ // Intentionally let the app return "false" to confirm that IME still receives "true".
+ final boolean returnedResult = false;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean setImeConsumesInput(boolean imeConsumesInput) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putBoolean("imeConsumesInput", imeConsumesInput);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callSetImeConsumesInput(expectedImeConsumesInput);
+ assertTrue("setImeConsumesInput() always returns true unless RemoteException is thrown",
+ expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+ methodCallVerifier.expectCalledOnce(args -> {
+ final boolean actualImeConsumesInput = args.getBoolean("imeConsumesInput");
+ assertEquals(expectedImeConsumesInput, actualImeConsumesInput);
+ }, TIMEOUT);
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#setImeConsumesInput(boolean)} fails fast once
+ * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+ */
+ @Test
+ public void testSetImeConsumesInputAfterUnbindInput() throws Exception {
+ final boolean returnedResult = true;
+
+ final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean setImeConsumesInput(boolean imeConsumesInput) {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putBoolean("imeConsumesInput", imeConsumesInput);
+ });
+ return returnedResult;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ // Memorize the current InputConnection.
+ expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+ // Let unbindInput happen.
+ triggerUnbindInput();
+ expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+ // Now this API call on the memorized IC should fail fast.
+ final ImeCommand command = session.callSetImeConsumesInput(true);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ // CAVEAT: this behavior is a bit questionable and may change in a future version.
+ assertTrue("Currently IC#setImeConsumesInput() still returns true even after "
+ + "unbindInput().", result.getReturnBooleanValue());
+ expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+ // Make sure that the app does not receive the call (for a while).
+ methodCallVerifier.expectNotCalled(
+ "Once unbindInput() happened, IC#setImeConsumesInput() fails fast.",
+ EXPECTED_NOT_CALLED_TIMEOUT);
+ });
+ }
+
+ /**
+ * Verify that the default implementation of
+ * {@link InputConnection#setImeConsumesInput(boolean)} returns {@code true} without any crash
+ * even when the target app does not override it.
+ */
+ @Test
+ public void testSetImeConsumesInputDefaultMethod() throws Exception {
+ testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callSetImeConsumesInput(true);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+ assertTrue("IC#setImeConsumesInput() still returns true even when the target "
+ + "application does not implement it.", result.getReturnBooleanValue());
+ });
+ }
+
+ /**
+ * Test {@link InputConnection#takeSnapshot()} is ignored as expected.
+ */
+ @Test
+ public void testTakeSnapshot() throws Exception {
+ final TextSnapshot returnedTextSnapshot = new TextSnapshot(
+ new SurroundingText("test", 4, 4, 0), -1, -1, 0);
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public TextSnapshot takeSnapshot() {
+ return returnedTextSnapshot;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final ImeCommand command = session.callTakeSnapshot();
+ assertTrue("takeSnapshot() always returns null",
+ expectCommand(stream, command, TIMEOUT).isNullReturnValue());
+ });
+ }
+
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionHandlerTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionHandlerTest.java
new file mode 100644
index 0000000..9939e42
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionHandlerTest.java
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
+import static android.view.inputmethod.cts.util.TestUtils.runOnMainSync;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
+import android.os.SystemClock;
+import android.platform.test.annotations.LargeTest;
+import android.system.Os;
+import android.text.InputType;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.SurroundingText;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.HandlerInputConnection;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.mockime.ImeCommand;
+import com.android.cts.mockime.ImeEvent;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Tests the thread-affinity in {@link InputConnection} callbacks provided by
+ * {@link InputConnection#getHandler()}.
+ *
+ * <p>TODO: Add more tests.</p>
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class InputConnectionHandlerTest extends EndToEndImeTestBase {
+ private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+
+ /**
+ * The value used in android.inputmethodservice.RemoteInputConnection#MAX_WAIT_TIME_MILLIS.
+ *
+ * <p>Although this is not a strictly-enforced timeout for all the Android devices, hopefully
+ * it'd be acceptable to assume that IMEs can receive result within 2 second even on slower
+ * devices.</p>
+ *
+ * <p>TODO: Consider making this as a test API.</p>
+ */
+ private static final long TIMEOUT_IN_REMOTE_INPUT_CONNECTION =
+ TimeUnit.MILLISECONDS.toMillis(2000);
+
+ private static final int TEST_VIEW_HEIGHT = 10;
+
+ private static final String TEST_MARKER_PREFIX =
+ "android.view.inputmethod.cts.InputConnectionHandlerTest";
+
+ private static String getTestMarker() {
+ return TEST_MARKER_PREFIX + "/" + SystemClock.elapsedRealtimeNanos();
+ }
+
+ private static final class InputConnectionHandlingThread extends HandlerThread
+ implements AutoCloseable {
+
+ private final Handler mHandler;
+
+ InputConnectionHandlingThread() {
+ super("IC-callback");
+ start();
+ mHandler = Handler.createAsync(getLooper());
+ }
+
+ @NonNull
+ Handler getHandler() {
+ return mHandler;
+ }
+
+ @Override
+ public void close() {
+ quitSafely();
+ try {
+ join(TIMEOUT);
+ } catch (InterruptedException e) {
+ fail("Failed to stop the thread: " + e);
+ }
+ }
+ }
+
+ /**
+ * A mostly-minimum implementation of {@link View} that can be used to test custom
+ * implementations of {@link View#onCreateInputConnection(EditorInfo)}.
+ */
+ static class TestEditor extends View {
+ TestEditor(@NonNull Context context) {
+ super(context);
+ setBackgroundColor(Color.YELLOW);
+ setFocusableInTouchMode(true);
+ setFocusable(true);
+ setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, TEST_VIEW_HEIGHT));
+ }
+ }
+
+ /**
+ * Test {@link InputConnection#commitText(CharSequence, int)} respects
+ * {@link InputConnection#getHandler()}.
+ */
+ @Test
+ public void testCommitText() throws Exception {
+ try (InputConnectionHandlingThread thread = new InputConnectionHandlingThread();
+ MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder())) {
+
+ final AtomicInteger callingThreadId = new AtomicInteger(0);
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ final class MyInputConnection extends HandlerInputConnection {
+ MyInputConnection() {
+ super(thread.getHandler());
+ }
+
+ @Override
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ callingThreadId.set(Os.gettid());
+ latch.countDown();
+ return super.commitText(text, newCursorPosition);
+ }
+ }
+
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ final String marker = getTestMarker();
+
+ TestActivity.startSync(activity -> {
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ // Just to be conservative, we explicitly check MockImeSession#isActive() here when
+ // injecting our custom InputConnection implementation.
+ final TestEditor testEditor = new TestEditor(activity) {
+ @Override
+ public boolean onCheckIsTextEditor() {
+ return imeSession.isActive();
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ if (imeSession.isActive()) {
+ outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
+ outAttrs.privateImeOptions = marker;
+ return new MyInputConnection();
+ }
+ return null;
+ }
+ };
+
+ testEditor.requestFocus();
+ layout.addView(testEditor);
+ return layout;
+ });
+
+ // Wait until the MockIme gets bound to the TestActivity.
+ expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+ // Wait until "onStartInput" gets called for the EditText.
+ expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
+ final ImeCommand command = imeSession.callCommitText("", 1);
+ expectCommand(stream, command, TIMEOUT);
+
+ assertTrue("commitText() must be called", latch.await(TIMEOUT, TimeUnit.MILLISECONDS));
+
+ assertEquals("commitText() must happen on the handler thread",
+ thread.getThreadId(), callingThreadId.get());
+ }
+ }
+
+ /**
+ * Test {@link InputConnection#reportFullscreenMode(boolean)} respects
+ * {@link InputConnection#getHandler()}.
+ */
+ @Test
+ public void testReportFullscreenMode() throws Exception {
+ try (InputConnectionHandlingThread thread = new InputConnectionHandlingThread();
+ MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder().setFullscreenModePolicy(
+ ImeSettings.FullscreenModePolicy.FORCE_FULLSCREEN))) {
+
+ final AtomicInteger callingThreadId = new AtomicInteger(0);
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ final class MyInputConnection extends HandlerInputConnection {
+ MyInputConnection() {
+ super(thread.getHandler());
+ }
+
+ @Override
+ public boolean reportFullscreenMode(boolean enabled) {
+ callingThreadId.set(Os.gettid());
+ latch.countDown();
+ return true;
+ }
+ }
+
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ final String marker = getTestMarker();
+
+ final AtomicReference<View> testEditorViewRef = new AtomicReference<>();
+ TestActivity.startSync(activity -> {
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ // Just to be conservative, we explicitly check MockImeSession#isActive() here when
+ // injecting our custom InputConnection implementation.
+ final TestEditor testEditor = new TestEditor(activity) {
+ @Override
+ public boolean onCheckIsTextEditor() {
+ return imeSession.isActive();
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ if (imeSession.isActive()) {
+ outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
+ outAttrs.privateImeOptions = marker;
+ return new MyInputConnection();
+ }
+ return null;
+ }
+ };
+
+ testEditor.requestFocus();
+ testEditorViewRef.set(testEditor);
+ layout.addView(testEditor);
+ return layout;
+ });
+
+ // Wait until the MockIme gets bound to the TestActivity.
+ expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+ expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
+ assertFalse("InputMethodManager#isFullscreenMode() must return false",
+ getOnMainSync(() -> InstrumentationRegistry.getInstrumentation().getContext()
+ .getSystemService(InputMethodManager.class).isFullscreenMode()));
+
+ // In order to have an IME be shown in the fullscreen mode,
+ // SOFT_INPUT_STATE_ALWAYS_VISIBLE is insufficient. An explicit API call is necessary.
+ runOnMainSync(() -> {
+ final View editor = testEditorViewRef.get();
+ editor.getContext().getSystemService(InputMethodManager.class)
+ .showSoftInput(editor, 0);
+ });
+
+ expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+
+ assertTrue("reportFullscreenMode() must be called",
+ latch.await(TIMEOUT, TimeUnit.MILLISECONDS));
+
+ assertEquals("reportFullscreenMode() must happen on the handler thread",
+ thread.getThreadId(), callingThreadId.get());
+
+ assertTrue("InputMethodManager#isFullscreenMode() must return true",
+ getOnMainSync(() -> InstrumentationRegistry.getInstrumentation().getContext()
+ .getSystemService(InputMethodManager.class).isFullscreenMode()));
+ }
+ }
+
+ /**
+ * A holder of {@link Handler} that is bound to a background {@link Looper} where
+ * {@link Throwable} thrown from tasks running there will be just ignored instead of triggering
+ * process crashes.
+ */
+ private static final class ErrorSwallowingHandlerThread implements AutoCloseable {
+ @NonNull
+ private final Handler mHandler;
+
+ @NonNull
+ Handler getHandler() {
+ return mHandler;
+ }
+
+ @NonNull
+ static ErrorSwallowingHandlerThread create() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicReference<Looper> mLooperRef = new AtomicReference<>();
+ new Thread(() -> {
+ Looper.prepare();
+ mLooperRef.set(Looper.myLooper());
+ latch.countDown();
+
+ while (true) {
+ try {
+ Looper.loop();
+ return;
+ } catch (Throwable ignore) {
+ }
+ }
+ }).start();
+
+ try {
+ assertTrue(latch.await(TIMEOUT, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ fail("Failed to create a Handler thread");
+ }
+
+ final Handler handler = Handler.createAsync(mLooperRef.get());
+ return new ErrorSwallowingHandlerThread(handler);
+ }
+
+ private ErrorSwallowingHandlerThread(@NonNull Handler handler) {
+ mHandler = handler;
+ }
+
+ @Override
+ public void close() {
+ mHandler.getLooper().quitSafely();
+ try {
+ mHandler.getLooper().getThread().join(TIMEOUT);
+ } catch (InterruptedException e) {
+ fail("Failed to terminate the thread");
+ }
+ }
+ }
+
+ /**
+ * Ensures that {@code event}'s elapse time is less than the given threshold.
+ *
+ * @param event {@link ImeEvent} to be tested.
+ * @param elapseThresholdInMilliSecond threshold in milli sec.
+ */
+ private static void expectElapseTimeLessThan(@NonNull ImeEvent event,
+ long elapseThresholdInMilliSecond) {
+ final long elapseTimeInMilli = TimeUnit.NANOSECONDS.toMillis(
+ event.getExitTimestamp() - event.getEnterTimestamp());
+ if (elapseTimeInMilli > elapseThresholdInMilliSecond) {
+ fail(event.getEventName() + " took " + elapseTimeInMilli + " msec,"
+ + " which must be less than " + elapseThresholdInMilliSecond + " msec.");
+ }
+ }
+
+ /**
+ * Test {@link InputConnection#getSurroundingText(int, int, int)} that throws an exception.
+ */
+ @Test
+ public void testExceptionFromGetSurroundingText() throws Exception {
+ try (ErrorSwallowingHandlerThread handlerThread = ErrorSwallowingHandlerThread.create();
+ MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder())) {
+
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ final class MyInputConnection extends HandlerInputConnection {
+ MyInputConnection() {
+ super(handlerThread.getHandler());
+ }
+
+ @Override
+ public SurroundingText getSurroundingText(int beforeLength, int afterLength,
+ int flags) {
+ latch.countDown();
+ throw new RuntimeException("Exception!");
+ }
+
+ }
+
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ final String marker = getTestMarker();
+
+ TestActivity.startSync(activity -> {
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ // Just to be conservative, we explicitly check MockImeSession#isActive() here when
+ // injecting our custom InputConnection implementation.
+ final TestEditor testEditor = new TestEditor(activity) {
+ @Override
+ public boolean onCheckIsTextEditor() {
+ return imeSession.isActive();
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ if (imeSession.isActive()) {
+ outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
+ outAttrs.privateImeOptions = marker;
+ return new MyInputConnection();
+ }
+ return null;
+ }
+ };
+
+ testEditor.requestFocus();
+ layout.addView(testEditor);
+ return layout;
+ });
+
+ // Wait until the MockIme gets bound to the TestActivity.
+ expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+ // Wait until "onStartInput" gets called for the EditText.
+ expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
+ final ImeCommand command = imeSession.callGetSurroundingText(1, 1, 0);
+ final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+
+ assertTrue("IC#getSurroundingText() must be called",
+ latch.await(TIMEOUT, TimeUnit.MILLISECONDS));
+
+ assertTrue("Exceptions from IC#getSurroundingText() must be interpreted as null.",
+ result.isNullReturnValue());
+ expectElapseTimeLessThan(result, TIMEOUT_IN_REMOTE_INPUT_CONNECTION);
+ }
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java
index 5eaa32e..9b1c836 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java
@@ -207,7 +207,7 @@
PackageManager.FEATURE_INPUT_METHODS));
enableImes(MOCK_IME_ID, HIDDEN_FROM_PICKER_IME_ID);
- final TestActivity testActivity = TestActivity.startSync(activity -> {
+ TestActivity.startSync(activity -> {
final View view = new View(activity);
view.setLayoutParams(new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
@@ -222,9 +222,8 @@
// Test InputMethodManager#showInputMethodPicker() works as expected.
mImManager.showInputMethodPicker();
- waitOnMainUntil(() -> mImManager.isInputMethodPickerShown()
- && !testActivity.hasWindowFocus(), TIMEOUT,
- "InputMethod picker should be shown and test activity lost focus");
+ waitOnMainUntil(() -> mImManager.isInputMethodPickerShown(), TIMEOUT,
+ "InputMethod picker should be shown");
final UiDevice uiDevice =
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
assertThat(uiDevice.wait(Until.hasObject(By.text(MOCK_IME_LABEL)), TIMEOUT)).isTrue();
@@ -235,8 +234,6 @@
new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND));
waitOnMainUntil(() -> !mImManager.isInputMethodPickerShown(), TIMEOUT,
"InputMethod picker should be closed");
- waitOnMainUntil(() -> testActivity.hasWindowFocus(), TIMEOUT,
- "Activity should be focused after picker dismissed");
}
private void enableImes(String... ids) {
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodPickerTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodPickerTest.java
new file mode 100644
index 0000000..3064c30
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodPickerTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
+import static android.content.pm.PackageManager.FEATURE_INPUT_METHODS;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.inputmethod.cts.util.TestUtils.waitOnMainUntil;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Intent;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.SecurityTest;
+import android.server.wm.ActivityManagerTestBase;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.LinearLayout;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class InputMethodPickerTest extends ActivityManagerTestBase {
+
+ private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+
+ private InputMethodManager mImManager;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mImManager = mContext.getSystemService(InputMethodManager.class);
+
+ closeSystemDialogsAndWait();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ closeSystemDialogsAndWait();
+ }
+
+ @AppModeFull(reason = "Instant apps cannot rely on ACTION_CLOSE_SYSTEM_DIALOGS")
+ @SecurityTest(minPatchLevel = "unknown")
+ @Test
+ public void testInputMethodPicker_hidesUntrustedOverlays() throws Exception {
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(FEATURE_INPUT_METHODS));
+ TestActivity testActivity = TestActivity.startSync(activity -> {
+ final View view = new View(activity);
+ view.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ return view;
+ });
+
+ // Test setup: Show overlay and verify that it worked.
+ getInstrumentation().runOnMainSync(() -> {
+ testActivity.showOverlayWindow();
+ });
+ mWmState.waitAndAssertWindowSurfaceShown(TestActivity.OVERLAY_WINDOW_NAME, true);
+
+ // Test setup: Show the IME picker and verify that it worked.
+ getInstrumentation().runOnMainSync(() -> {
+ mImManager.showInputMethodPicker();
+ });
+ waitOnMainUntil(() -> mImManager.isInputMethodPickerShown(), TIMEOUT,
+ "Test setup failed: InputMethod picker should be shown");
+
+ // Actual Test: Make sure the IME picker hides app overlays.
+ mWmState.waitAndAssertWindowSurfaceShown(TestActivity.OVERLAY_WINDOW_NAME, false);
+ }
+
+ private void closeSystemDialogsAndWait() throws Exception {
+ mContext.sendBroadcast(
+ new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND));
+ waitOnMainUntil(() -> !mImManager.isInputMethodPickerShown(), TIMEOUT,
+ "Test assertion failed: InputMethod picker should be closed but isn't");
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
index 6eeb1da..cfffd9d 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
@@ -18,6 +18,8 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible;
@@ -31,6 +33,7 @@
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectNoImeCrash;
import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
import static com.android.cts.mockime.ImeEventStreamTestUtils.verificationMatcher;
@@ -57,6 +60,7 @@
import android.view.inputmethod.InputConnectionWrapper;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.SimulatedVirtualDisplaySession;
import android.view.inputmethod.cts.util.TestActivity;
import android.view.inputmethod.cts.util.TestActivity2;
import android.view.inputmethod.cts.util.TestUtils;
@@ -98,6 +102,8 @@
public class InputMethodServiceTest extends EndToEndImeTestBase {
private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(20);
private static final long EXPECTED_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
+ private static final long ACTIVITY_LAUNCH_INTERVAL = 500; // msec
+
private static final String ERASE_FONT_SCALE_CMD = "settings delete system font_scale";
// 1.2 is an arbitrary value.
@@ -126,11 +132,15 @@
mInstrumentation = InstrumentationRegistry.getInstrumentation();
}
- private TestActivity createTestActivity(final int windowFlags) {
+ private TestActivity createTestActivity(int windowFlags) {
return TestActivity.startSync(activity -> createLayout(windowFlags, activity));
}
- private TestActivity createTestActivity2(final int windowFlags) {
+ private TestActivity createTestActivity(int windowFlags, int displayId) throws Exception {
+ return TestActivity.startSync(displayId, activity -> createLayout(windowFlags, activity));
+ }
+
+ private TestActivity createTestActivity2(int windowFlags) {
return TestActivity2.startSync(activity -> createLayout(windowFlags, activity));
}
@@ -502,6 +512,7 @@
try (MockImeSession imeSession = MockImeSession.create(
mInstrumentation.getContext(), mInstrumentation.getUiAutomation(),
new ImeSettings.Builder().setVerifyUiContextApisInOnCreate(true))) {
+ ensureImeRunning();
final ImeEventStream stream = imeSession.openEventStream();
// Verify if getDisplay doesn't throw exception before InputMethodService's
@@ -699,6 +710,7 @@
try (MockImeSession imeSession = MockImeSession.create(
mInstrumentation.getContext(), mInstrumentation.getUiAutomation(),
new ImeSettings.Builder().setVerifyUiContextApisInOnCreate(true))) {
+ ensureImeRunning();
final ImeEventStream stream = imeSession.openEventStream();
// Verify if InputMethodService#isUiContext returns true in #onCreate
@@ -730,6 +742,31 @@
}
}
+ @Test
+ public void testNoExceptionWhenSwitchingDisplaysWithImeReCreate() throws Exception {
+ try (SimulatedVirtualDisplaySession displaySession = SimulatedVirtualDisplaySession.create(
+ mInstrumentation.getContext(), 800, 600, 240, DISPLAY_IME_POLICY_LOCAL);
+ MockImeSession imeSession = MockImeSession.create(
+ mInstrumentation.getContext(), mInstrumentation.getUiAutomation(),
+ new ImeSettings.Builder())) {
+ // Launch activity repeatedly with re-create / showing IME on different displays
+ for (int i = 0; i < 10; i++) {
+ int displayId = (i % 2 == 0) ? displaySession.getDisplayId() : DEFAULT_DISPLAY;
+ createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE, displayId);
+ SystemClock.sleep(ACTIVITY_LAUNCH_INTERVAL);
+ }
+ // Verify no crash and onCreate / onDestroy keeps paired from MockIme event stream
+ expectNoImeCrash(imeSession, TIMEOUT);
+ }
+ }
+
+ /** Explicitly start-up the IME process if it would have been prevented. */
+ protected void ensureImeRunning() {
+ if (isPreventImeStartup()) {
+ createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+ }
+ }
+
/** Test case for committing and setting composing region after cursor. */
private static UpdateSelectionTest getCommitAndSetComposingRegionTest(
long timeout, String makerPrefix) throws Exception {
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java
index 4eb6573..24a0e09 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java
@@ -27,6 +27,8 @@
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@@ -34,14 +36,23 @@
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.graphics.Color;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
import android.os.Process;
import android.os.SystemClock;
import android.platform.test.annotations.AppModeFull;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.Selection;
import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.TextSnapshot;
import android.view.inputmethod.cts.util.DisableScreenDozeRule;
import android.view.inputmethod.cts.util.EndToEndImeTestBase;
import android.view.inputmethod.cts.util.RequireImeCompatFlagRule;
@@ -52,6 +63,7 @@
import android.widget.EditText;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -70,6 +82,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
import java.util.function.Predicate;
@MediumTest
@@ -84,6 +97,14 @@
FINISH_INPUT_NO_FALLBACK_CONNECTION, true);
private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+ private static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+
+ private static final String TEST_MARKER_PREFIX =
+ "android.view.inputmethod.cts.FocusHandlingTest";
+
+ private static String getTestMarker() {
+ return TEST_MARKER_PREFIX + "/" + SystemClock.elapsedRealtimeNanos();
+ }
@AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
@Test
@@ -99,8 +120,7 @@
context, instrumentation.getUiAutomation(), new ImeSettings.Builder())) {
final ImeEventStream stream = imeSession.openEventStream();
- final String marker = InputMethodManagerTest.class.getName() + "/"
- + SystemClock.elapsedRealtimeNanos();
+ final String marker = getTestMarker();
final AtomicInteger screenStateCallbackRef = new AtomicInteger(-1);
TestActivity.startSync(activity -> {
final LinearLayout layout = new LinearLayout(activity);
@@ -187,8 +207,7 @@
new ImeSettings.Builder())) {
final ImeEventStream stream = imeSession.openEventStream();
- final String marker = InputMethodStartInputLifecycleTest.class.getName() + "/"
- + SystemClock.elapsedRealtimeNanos();
+ final String marker = getTestMarker();
final EditText editText = launchTestActivity(marker);
TestUtils.runOnMainSync(() -> editText.requestFocus());
@@ -243,6 +262,265 @@
return editTextRef.get();
}
+ /**
+ * A mostly-minimum implementation of {@link View} that can be used to test custom
+ * implementations of {@link View#onCreateInputConnection(EditorInfo)}.
+ */
+ static class TestEditor extends View {
+ TestEditor(@NonNull Context context) {
+ super(context);
+ setBackgroundColor(Color.YELLOW);
+ setFocusableInTouchMode(true);
+ setFocusable(true);
+ setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, 10 /* height */));
+ }
+ }
+
+ private abstract static class TestInputConnection extends BaseInputConnection {
+ @NonNull
+ private final Editable mEditable;
+
+ TestInputConnection(@NonNull View view, @NonNull Editable editable) {
+ super(view, true /* fullEditor */);
+ mEditable = editable;
+ }
+
+ @NonNull
+ @Override
+ public final Editable getEditable() {
+ return mEditable;
+ }
+ }
+
+ @Test
+ public void testInvalidateInput() throws Exception {
+ // If IC#takeSnapshot() returns true, it should work, even if IC#{begin,end}BatchEdit()
+ // always return false.
+ expectNativeInvalidateInput((view, editable) -> new TestInputConnection(view, editable) {
+ @Override
+ public boolean beginBatchEdit() {
+ return false;
+ }
+ @Override
+ public boolean endBatchEdit() {
+ return false;
+ }
+ });
+
+ // Of course IMM#invalidateInput() should just work for ICs that support
+ // {begin,end}BatchEdit().
+ expectNativeInvalidateInput((view, editable) -> new TestInputConnection(view, editable) {
+ private int mBatchEditCount = 0;
+ @Override
+ public boolean beginBatchEdit() {
+ ++mBatchEditCount;
+ return true;
+ }
+ @Override
+ public boolean endBatchEdit() {
+ if (mBatchEditCount <= 0) {
+ return false;
+ }
+ --mBatchEditCount;
+ return mBatchEditCount > 0;
+ }
+ });
+
+ // If IC#takeSnapshot() returns false, then fall back to IMM#restartInput()
+ expectFallbackInvalidateInput((view, editable) -> new TestInputConnection(view, editable) {
+ @Override
+ public boolean beginBatchEdit() {
+ return false;
+ }
+ @Override
+ public boolean endBatchEdit() {
+ return false;
+ }
+ @Override
+ public TextSnapshot takeSnapshot() {
+ return null;
+ }
+ });
+
+ // Bug 209958658 should not prevent the system from using the native invalidateInput().
+ expectNativeInvalidateInput((view, editable) -> new TestInputConnection(view, editable) {
+ private int mBatchEditCount = 0;
+
+ @Override
+ public boolean beginBatchEdit() {
+ ++mBatchEditCount;
+ return true;
+ }
+ @Override
+ public boolean endBatchEdit() {
+ if (mBatchEditCount <= 0) {
+ return false;
+ }
+ --mBatchEditCount;
+ // This violates the spec. See Bug 209958658 for instance.
+ return true;
+ }
+ });
+
+ // Even if IC#endBatchEdit() never returns false, the system should be able to fall back
+ // to IMM#restartInput(). This is a regression test for Bug 208941904.
+ expectFallbackInvalidateInput((view, editable) -> new TestInputConnection(view, editable) {
+ @Override
+ public boolean beginBatchEdit() {
+ return true;
+ }
+ @Override
+ public boolean endBatchEdit() {
+ return true;
+ }
+ });
+ }
+
+ private void expectNativeInvalidateInput(
+ BiFunction<View, Editable, InputConnection> inputConnectionProvider) throws Exception {
+ testInvalidateInputMain(true, inputConnectionProvider);
+ }
+
+ private void expectFallbackInvalidateInput(
+ BiFunction<View, Editable, InputConnection> inputConnectionProvider) throws Exception {
+ testInvalidateInputMain(false, inputConnectionProvider);
+ }
+
+ private void testInvalidateInputMain(boolean expectNativeInvalidateInput,
+ BiFunction<View, Editable, InputConnection> inputConnectionProvider) throws Exception {
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ try (MockImeSession imeSession = MockImeSession.create(
+ instrumentation.getContext(),
+ instrumentation.getUiAutomation(),
+ new ImeSettings.Builder())) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ final String marker = getTestMarker();
+ final int initialSelStart = 3;
+ final int initialSelEnd = 7;
+ final int initialCapsMode = TextUtils.CAP_MODE_SENTENCES;
+
+ final AtomicInteger onCreateConnectionCount = new AtomicInteger(0);
+ class MyTestEditor extends TestEditor {
+ final Editable mEditable;
+
+ MyTestEditor(Context context, @NonNull Editable editable) {
+ super(context);
+ mEditable = editable;
+ }
+
+ @Override
+ public boolean onCheckIsTextEditor() {
+ return true;
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ onCreateConnectionCount.incrementAndGet();
+ outAttrs.inputType =
+ InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
+ outAttrs.initialSelStart = Selection.getSelectionStart(mEditable);
+ outAttrs.initialSelEnd = Selection.getSelectionEnd(mEditable);
+ outAttrs.initialCapsMode = initialCapsMode;
+ outAttrs.privateImeOptions = marker;
+ outAttrs.setInitialSurroundingText(mEditable);
+ return inputConnectionProvider.apply(this, mEditable);
+ }
+ }
+
+ final AtomicReference<MyTestEditor> myEditorRef = new AtomicReference<>();
+ TestActivity.startSync(activity -> {
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ final Editable editable =
+ Editable.Factory.getInstance().newEditable("0123456789");
+ Selection.setSelection(editable, initialSelStart, initialSelEnd);
+
+ final MyTestEditor editor = new MyTestEditor(activity, editable);
+ editor.requestFocus();
+ myEditorRef.set(editor);
+
+ layout.addView(editor);
+ return layout;
+ });
+ final MyTestEditor myEditor = myEditorRef.get();
+
+ // Wait until the MockIme gets bound to the TestActivity.
+ expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+ {
+ final ImeEvent startInputEvent =
+ expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+ final EditorInfo editorInfo =
+ startInputEvent.getArguments().getParcelable("editorInfo");
+ assertThat(editorInfo).isNotNull();
+ assertThat(editorInfo.initialSelStart).isEqualTo(initialSelStart);
+ assertThat(editorInfo.initialSelEnd).isEqualTo(initialSelEnd);
+ assertThat(editorInfo.getInitialSelectedText(0).toString()).isEqualTo("3456");
+ }
+
+ stream.skipAll();
+ final ImeEventStream forkedStream = stream.copy();
+
+ final int prevOnCreateInputConnectionCount = onCreateConnectionCount.get();
+
+ final int newSelStart = 1;
+ final int newSelEnd = 3;
+ TestUtils.runOnMainSync(() -> {
+ Selection.setSelection(myEditor.mEditable, newSelStart, newSelEnd);
+ final InputMethodManager imm = myEditor.getContext().getSystemService(
+ InputMethodManager.class);
+ imm.invalidateInput(myEditor);
+ });
+
+ // Verify that InputMethodService#onStartInput() is triggered as if IMM#restartInput()
+ // was called.
+ {
+ final ImeEvent startInputEvent =
+ expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+ final boolean restarting = startInputEvent.getArguments().getBoolean("restarting");
+ assertThat(restarting).isTrue();
+ final EditorInfo editorInfo =
+ startInputEvent.getArguments().getParcelable("editorInfo");
+ assertThat(editorInfo).isNotNull();
+ assertThat(editorInfo.initialSelStart).isEqualTo(newSelStart);
+ assertThat(editorInfo.initialSelEnd).isEqualTo(newSelEnd);
+ assertThat(editorInfo.getInitialSelectedText(0).toString()).isEqualTo("12");
+ }
+
+ if (expectNativeInvalidateInput) {
+ // If InputMethodManager#interruptInput() is expected to be natively supported,
+ // additional View#onCreateInputConnection() must not happen.
+ assertThat(onCreateConnectionCount.get()).isEqualTo(
+ prevOnCreateInputConnectionCount);
+ } else {
+ // InputMethodManager#interruptInput() is expected to be falling back into
+ // InputMethodManager#restartInput(), which triggers View#onCreateInputConnection()
+ // as a consequence.
+ assertThat(onCreateConnectionCount.get()).isGreaterThan(
+ prevOnCreateInputConnectionCount);
+ }
+
+ // For historical reasons, InputMethodService#onFinishInput() will not be triggered when
+ // restarting an input connection.
+ assertThat(forkedStream.findFirst(onFinishInputMatcher()).isPresent()).isFalse();
+
+ // Make sure that InputMethodManager#updateSelection() will be ignored when there is
+ // no change from the last call of InputMethodManager#interruptInput().
+ TestUtils.runOnMainSync(() -> {
+ Selection.setSelection(myEditor.mEditable, newSelStart, newSelEnd);
+ final InputMethodManager imm = myEditor.getContext().getSystemService(
+ InputMethodManager.class);
+ imm.updateSelection(myEditor, newSelStart, newSelEnd, -1, -1);
+ });
+
+ notExpectEvent(stream, event -> "onUpdateSelection".equals(event.getEventName()),
+ NOT_EXPECT_TIMEOUT);
+ }
+ }
+
private static Predicate<ImeEvent> onFinishInputMatcher() {
return event -> TextUtils.equals("onFinishInput", event.getEventName());
}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
index 673c87a..65a0c30 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
@@ -45,6 +45,7 @@
import android.app.Instrumentation;
import android.content.pm.PackageManager;
import android.graphics.Color;
+import android.graphics.Insets;
import android.os.SystemClock;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.AppModeInstant;
@@ -55,6 +56,7 @@
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
+import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
@@ -76,18 +78,25 @@
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
+import com.android.compatibility.common.util.FeatureUtil;
import com.android.cts.mockime.ImeEvent;
import com.android.cts.mockime.ImeEventStream;
import com.android.cts.mockime.ImeLayoutInfo;
import com.android.cts.mockime.ImeSettings;
import com.android.cts.mockime.MockImeSession;
+import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Map;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
@@ -358,21 +367,16 @@
}
@Test
- public void testImeVisibilityWhenDismisingDialogWithImeFocused() throws Exception {
+ public void testImeVisibilityWhenDismissingDialogWithImeFocused() throws Exception {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- final InputMethodManager imm = instrumentation.getTargetContext().getSystemService(
- InputMethodManager.class);
try (MockImeSession imeSession = MockImeSession.create(
- InstrumentationRegistry.getInstrumentation().getContext(),
- InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ instrumentation.getContext(),
+ instrumentation.getUiAutomation(),
new ImeSettings.Builder())) {
final ImeEventStream stream = imeSession.openEventStream();
// Launch a simple test activity
- final TestActivity testActivity = TestActivity.startSync(activity -> {
- final LinearLayout layout = new LinearLayout(activity);
- return layout;
- });
+ final TestActivity testActivity = TestActivity.startSync(LinearLayout::new);
// Launch a dialog
final String marker = getTestMarker();
@@ -427,11 +431,14 @@
expectImeInvisible(TIMEOUT);
// Expect fallback input connection started and keyboard invisible after activity
- // focused.
- final ImeEvent onStart = expectEvent(stream,
- event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
- assertTrue(onStart.getEnterState().hasFallbackInputConnection());
- TestUtils.waitOnMainUntil(() -> testActivity.hasWindowFocus(), TIMEOUT);
+ // focused unless avoidable keyboard startup is desired,
+ // in which case, no fallback will be started.
+ if (!isPreventImeStartup()) {
+ final ImeEvent onStart = expectEvent(stream,
+ event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+ assertTrue(onStart.getEnterState().hasFallbackInputConnection());
+ }
+ TestUtils.waitOnMainUntil(testActivity::hasWindowFocus, TIMEOUT);
expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
View.GONE, TIMEOUT);
expectImeInvisible(TIMEOUT);
@@ -560,12 +567,16 @@
@AppModeFull
@Test
public void testImeVisibilityWhenImeTransitionBetweenActivities_Full() throws Exception {
+ // TODO(b/210041952): Temporary disable testing on TV platform until realize how to address.
+ Assume.assumeFalse(FeatureUtil.isTV());
runImeVisibilityWhenImeTransitionBetweenActivities(false /* instant */);
}
@AppModeInstant
@Test
public void testImeVisibilityWhenImeTransitionBetweenActivities_Instant() throws Exception {
+ // TODO(b/210041952): Temporary disable testing on TV platform until realize how to address.
+ Assume.assumeFalse(FeatureUtil.isTV());
runImeVisibilityWhenImeTransitionBetweenActivities(true /* instant */);
}
@@ -718,20 +729,33 @@
View.VISIBLE, TIMEOUT);
expectImeVisible(TIMEOUT);
- // Launcher another test activity from another process with popup dialog.
+ // Launch another test activity from another process with popup dialog.
MockTestActivityUtil.launchSync(instant, TIMEOUT,
Map.of(MockTestActivityUtil.EXTRA_KEY_SHOW_DIALOG, "true"));
+ BySelector dialogSelector = By.clazz(AlertDialog.class).depth(0);
+ UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ assertNotNull(uiDevice.wait(Until.hasObject(dialogSelector), TIMEOUT));
+
// Dismiss dialog and back to original test activity
MockTestActivityUtil.sendBroadcastAction(MockTestActivityUtil.EXTRA_DISMISS_DIALOG);
+ final CountDownLatch imeVisibilityUpdateLatch = new CountDownLatch(1);
+ AtomicReference<Boolean> imeInsetsVisible = new AtomicReference<>();
+ TestUtils.runOnMainSync(
+ () -> testActivity.getWindow().getDecorView().setOnApplyWindowInsetsListener(
+ (v, insets) -> {
+ if (insets.getInsets(WindowInsets.Type.ime()) != Insets.NONE) {
+ imeInsetsVisible.set(insets.isVisible(WindowInsets.Type.ime()));
+ imeVisibilityUpdateLatch.countDown();
+ }
+ return v.onApplyWindowInsets(insets);
+ }));
// Verify keyboard visibility should aligned with IME insets visibility.
TestUtils.waitOnMainUntil(
() -> testActivity.getWindow().getDecorView().getVisibility() == VISIBLE
&& testActivity.getWindow().getDecorView().hasWindowFocus(), TIMEOUT);
-
- AtomicReference<Boolean> imeInsetsVisible = new AtomicReference<>();
- TestUtils.runOnMainSync(() ->
- imeInsetsVisible.set(editTextRef.get().getRootWindowInsets().isVisible(ime())));
+ assertTrue("Waiting for onApplyWindowInsets timed out",
+ imeVisibilityUpdateLatch.await(5, TimeUnit.SECONDS));
if (imeInsetsVisible.get()) {
expectImeVisible(TIMEOUT);
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
index 162c428..7470047 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
@@ -17,6 +17,7 @@
package android.view.inputmethod.cts;
import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
@@ -48,6 +49,7 @@
import android.view.inputmethod.cts.util.NavigationBarInfo;
import android.view.inputmethod.cts.util.TestActivity;
import android.view.inputmethod.cts.util.UnlockScreenRule;
+import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -58,7 +60,6 @@
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.android.compatibility.common.util.ImeAwareEditText;
import com.android.cts.mockime.ImeEventStream;
import com.android.cts.mockime.ImeLayoutInfo;
import com.android.cts.mockime.ImeSettings;
@@ -140,12 +141,12 @@
case DIMMING_DIALOG_ABOVE_IME: {
final LinearLayout layout = new LinearLayout(activity);
layout.setOrientation(LinearLayout.VERTICAL);
- final ImeAwareEditText editText = new ImeAwareEditText(activity);
+ final EditText editText = new EditText(activity);
editText.setPrivateImeOptions(TEST_MARKER);
editText.setHint("editText");
editText.requestFocus();
- editText.scheduleShowSoftInput();
layout.addView(editText);
+ activity.getWindow().getDecorView().getWindowInsetsController().show(ime());
contentView = layout;
break;
}
@@ -193,17 +194,17 @@
}
case DIMMING_DIALOG_BEHIND_IME: {
final AlertDialog alertDialog = getOnMainSync(() -> {
- final ImeAwareEditText editText = new ImeAwareEditText(activity);
+ final EditText editText = new EditText(activity);
editText.setPrivateImeOptions(TEST_MARKER);
editText.setHint("editText");
editText.requestFocus();
- editText.scheduleShowSoftInput();
final AlertDialog dialog = new AlertDialog.Builder(activity)
.setView(editText)
.create();
dialog.getWindow().setFlags(FLAG_DIM_BEHIND,
FLAG_DIM_BEHIND | FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
dialog.show();
+ activity.getWindow().getDecorView().getWindowInsetsController().show(ime());
return dialog;
});
// Note: Dialog#dismiss() is a thread safe method so we don't need to call this from
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/StylusHandwritingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/StylusHandwritingTest.java
new file mode 100644
index 0000000..3ee8e74
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/StylusHandwritingTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.editorMatcher;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
+
+import static org.junit.Assert.assertTrue;
+
+import android.os.SystemClock;
+import android.util.Pair;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * IMF and end-to-end Stylus handwriting tests.
+ */
+public class StylusHandwritingTest extends EndToEndImeTestBase {
+ private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+ private static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+ private static final String TEST_MARKER_PREFIX =
+ "android.view.inputmethod.cts.StylusHandwritingTest";
+
+ @Test
+ public void testHandwritingStartAndFinish() throws Exception {
+ final InputMethodManager imm = InstrumentationRegistry.getInstrumentation()
+ .getTargetContext().getSystemService(InputMethodManager.class);
+ try (MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder())) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ final String marker = getTestMarker();
+ final EditText editText = launchTestActivity(marker);
+
+ expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+ notExpectEvent(
+ stream,
+ editorMatcher("onStartInputView", marker),
+ NOT_EXPECT_TIMEOUT);
+ imm.startStylusHandwriting(editText);
+ // keyboard shouldn't show up.
+ notExpectEvent(
+ stream,
+ editorMatcher("onStartInputView", marker),
+ NOT_EXPECT_TIMEOUT);
+
+ // Handwriting should start
+ expectEvent(
+ stream,
+ event -> "onStartStylusHandwriting".equals(event.getEventName()),
+ TIMEOUT);
+
+ // Verify Stylus Handwriting window is shown
+ assertTrue(expectCommand(
+ stream, imeSession.callGetStylusHandwritingWindowVisibility(), TIMEOUT)
+ .getReturnBooleanValue());
+
+ // Verify calling finishStylusHandwriting() calls onFinishStylusHandwriting().
+ imeSession.callFinishStylusHandwriting();
+ expectEvent(
+ stream,
+ event -> "onFinishStylusHandwriting".equals(event.getEventName()),
+ TIMEOUT);
+ }
+ }
+
+ private EditText launchTestActivity(@NonNull String marker) {
+ return launchTestActivity(marker, getTestMarker()).first;
+ }
+
+ private static String getTestMarker() {
+ return TEST_MARKER_PREFIX + "/" + SystemClock.elapsedRealtimeNanos();
+ }
+
+ private Pair<EditText, EditText> launchTestActivity(@NonNull String focusedMarker,
+ @NonNull String nonFocusedMarker) {
+ final AtomicReference<EditText> focusedEditTextRef = new AtomicReference<>();
+ final AtomicReference<EditText> nonFocusedEditTextRef = new AtomicReference<>();
+ TestActivity.startSync(activity -> {
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ final EditText focusedEditText = new EditText(activity);
+ focusedEditText.setHint("focused editText");
+ focusedEditText.setPrivateImeOptions(focusedMarker);
+ focusedEditText.requestFocus();
+ focusedEditTextRef.set(focusedEditText);
+ layout.addView(focusedEditText);
+
+ final EditText nonFocusedEditText = new EditText(activity);
+ nonFocusedEditText.setPrivateImeOptions(nonFocusedMarker);
+ nonFocusedEditText.setHint("target editText");
+ nonFocusedEditTextRef.set(nonFocusedEditText);
+ layout.addView(nonFocusedEditText);
+ return layout;
+ });
+ return new Pair<>(focusedEditTextRef.get(), nonFocusedEditTextRef.get());
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/TextAttributeTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/TextAttributeTest.java
new file mode 100644
index 0000000..3615e1f
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/TextAttributeTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.view.inputmethod.TextAttribute;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextAttributeTest {
+ private static final String SUGGESTION = "suggestion";
+ private static final String EXTRAS_KEY = "extras_key";
+ private static final String EXTRAS_VALUE = "extras_value";
+
+ private static final List<String> SUGGESTIONS = Collections.singletonList(SUGGESTION);
+ private static final PersistableBundle EXTRA_BUNDLE;
+ static {
+ final PersistableBundle bundle = new PersistableBundle();
+ bundle.putString(EXTRAS_KEY, EXTRAS_VALUE);
+ EXTRA_BUNDLE = bundle;
+ }
+
+ @Test
+ public void testTextAttribute() {
+ final TextAttribute textAttribute = new TextAttribute.Builder()
+ .setTextConversionSuggestions(SUGGESTIONS)
+ .setExtras(EXTRA_BUNDLE)
+ .build();
+ assertTextAttribute(textAttribute);
+ }
+
+ @Test
+ public void testWriteToParcel() {
+ final TextAttribute textAttribute = new TextAttribute.Builder()
+ .setTextConversionSuggestions(SUGGESTIONS)
+ .setExtras(EXTRA_BUNDLE)
+ .build();
+
+ assertTextAttribute(cloneViaParcel(textAttribute));
+ }
+
+ private static void assertTextAttribute(TextAttribute info) {
+ assertThat(info.getTextConversionSuggestions()).containsExactlyElementsIn(SUGGESTIONS);
+ assertThat(info.getExtras().getString(EXTRAS_KEY)).isEqualTo(EXTRAS_VALUE);
+ }
+
+ private static TextAttribute cloneViaParcel(TextAttribute src) {
+ final Parcel parcel = Parcel.obtain();
+ try {
+ src.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return TextAttribute.CREATOR.createFromParcel(parcel);
+ } finally {
+ parcel.recycle();
+ }
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/TextSnapshotTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/TextSnapshotTest.java
new file mode 100644
index 0000000..afd5c73
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/TextSnapshotTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.google.common.truth.Truth.assertThat;
+
+import android.text.InputType;
+import android.text.Selection;
+import android.view.inputmethod.SurroundingText;
+import android.view.inputmethod.TextSnapshot;
+import android.view.inputmethod.cts.util.InputConnectionTestUtils;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class TextSnapshotTest {
+
+ @Test
+ public void testConstruction() {
+ // Empty text
+ verify(surroundingText("[]", 0), -1, -1, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+
+ // A single character
+ verify(surroundingText("0[]]", 0), -1, -1, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+
+ verify(surroundingText("012[345]6789", 0), -1, -1, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ verify(surroundingText("012[345]6789", 1), -1, -1, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ verify(surroundingText("012[345]6789", 2), -1, -1, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+
+ verify(surroundingText("012[345]6789", 0), -1, -1, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ verify(surroundingText("012[345]6789", 0), 3, 6, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ verify(surroundingText("012[345]6789", 2), 3, 6, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testNullSurroundingText1() {
+ new TextSnapshot(null, -1, -1, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidComposeRegion() {
+ new TextSnapshot(surroundingText("012[345]6789", 0), -2, -2,
+ InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidComposeRegion2() {
+ new TextSnapshot(surroundingText("012[345]6789", 0), 0, -2,
+ InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidComposeRegion3() {
+ new TextSnapshot(surroundingText("012[345]6789", 0), -2, 0,
+ InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidComposeRegion4() {
+ new TextSnapshot(surroundingText("012[345]6789", 0), 2, 1,
+ InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ }
+
+ private static SurroundingText surroundingText(@NonNull String formatString, int offset) {
+ final CharSequence text = InputConnectionTestUtils.formatString(formatString);
+ final int selectionStart = Selection.getSelectionStart(text);
+ assertThat(selectionStart).isGreaterThan(-1);
+ final int selectionEnd = Selection.getSelectionEnd(text);
+ assertThat(selectionEnd).isGreaterThan(-1);
+ return new SurroundingText(text.subSequence(offset, text.length() - offset),
+ selectionStart - offset, selectionEnd - offset, offset);
+ }
+
+ private static void verify(@NonNull SurroundingText surroundingText, int compositionStart,
+ int compositionEnd, int cursorCapsMode) {
+ final TextSnapshot snapshot =
+ new TextSnapshot(surroundingText, compositionStart, compositionEnd, cursorCapsMode);
+
+ assertThat(snapshot.getSurroundingText().getText()).isEqualTo(surroundingText.getText());
+ assertThat(snapshot.getSurroundingText().getOffset())
+ .isEqualTo(surroundingText.getOffset());
+ assertThat(snapshot.getSurroundingText().getSelectionStart())
+ .isEqualTo(surroundingText.getSelectionStart());
+ assertThat(snapshot.getSurroundingText().getSelectionEnd())
+ .isEqualTo(surroundingText.getSelectionEnd());
+
+ assertThat(snapshot.getSelectionStart())
+ .isEqualTo(surroundingText.getSelectionStart() + surroundingText.getOffset());
+ assertThat(snapshot.getSelectionEnd())
+ .isEqualTo(surroundingText.getSelectionEnd() + surroundingText.getOffset());
+
+ assertThat(snapshot.getCompositionStart()).isEqualTo(compositionStart);
+ assertThat(snapshot.getCompositionEnd()).isEqualTo(compositionEnd);
+
+ assertThat(snapshot.getCursorCapsMode()).isEqualTo(cursorCapsMode);
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java
index f2a64c4..52a3de5 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java
@@ -20,8 +20,10 @@
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.AppModeInstant;
@@ -97,4 +99,15 @@
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
}
+
+ protected static boolean isPreventImeStartup() {
+ final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ try {
+ return context.getResources().getBoolean(
+ android.R.bool.config_preventImeStartupUnlessTextEditor);
+ } catch (Resources.NotFoundException e) {
+ // Assume this is not enabled.
+ return false;
+ }
+ }
}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/HandlerInputConnection.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/HandlerInputConnection.java
new file mode 100644
index 0000000..6992015
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/HandlerInputConnection.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts.util;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputContentInfo;
+import android.view.inputmethod.SurroundingText;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A null implementation of {@link InputConnection} except for {@link InputConnection#getHandler()}.
+ *
+ * <p>This is useful when testing callbacks on {@link InputConnection} are happening on the thread
+ * specified by {@link InputConnection#getHandler()}.</p>
+ */
+public class HandlerInputConnection implements InputConnection {
+ @Nullable
+ private final Handler mHandler;
+
+ protected HandlerInputConnection(@Nullable Handler handler) {
+ mHandler = handler;
+ }
+
+ @Override
+ public CharSequence getTextBeforeCursor(int n, int flags) {
+ return null;
+ }
+
+ @Override
+ public CharSequence getTextAfterCursor(int n, int flags) {
+ return null;
+ }
+
+ @Override
+ public CharSequence getSelectedText(int flags) {
+ return null;
+ }
+
+ @Override
+ public SurroundingText getSurroundingText(int beforeLength, int afterLength, int flags) {
+ return null;
+ }
+
+ @Override
+ public int getCursorCapsMode(int reqModes) {
+ return 0;
+ }
+
+ @Override
+ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+ return null;
+ }
+
+ @Override
+ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+ return false;
+ }
+
+ @Override
+ public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+ return false;
+ }
+
+ @Override
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ return false;
+ }
+
+ @Override
+ public boolean setComposingRegion(int start, int end) {
+ return false;
+ }
+
+ @Override
+ public boolean finishComposingText() {
+ return false;
+ }
+
+ @Override
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ return false;
+ }
+
+ @Override
+ public boolean commitCompletion(CompletionInfo text) {
+ return false;
+ }
+
+ @Override
+ public boolean commitCorrection(CorrectionInfo correctionInfo) {
+ return false;
+ }
+
+ @Override
+ public boolean setSelection(int start, int end) {
+ return false;
+ }
+
+ @Override
+ public boolean performEditorAction(int editorAction) {
+ return false;
+ }
+
+ @Override
+ public boolean performContextMenuAction(int id) {
+ return false;
+ }
+
+ @Override
+ public boolean beginBatchEdit() {
+ return false;
+ }
+
+ @Override
+ public boolean endBatchEdit() {
+ return false;
+ }
+
+ @Override
+ public boolean sendKeyEvent(KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean clearMetaKeyStates(int states) {
+ return false;
+ }
+
+ @Override
+ public boolean reportFullscreenMode(boolean enabled) {
+ return false;
+ }
+
+ @Override
+ public boolean performSpellCheck() {
+ return false;
+ }
+
+ @Override
+ public boolean performPrivateCommand(String action, Bundle data) {
+ return false;
+ }
+
+ @Override
+ public boolean requestCursorUpdates(int cursorUpdateMode) {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ @Override
+ public void closeConnection() {
+ }
+
+ @Override
+ public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
+ return false;
+ }
+
+ @Override
+ public boolean setImeConsumesInput(boolean imeConsumesInput) {
+ return false;
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java
index c245b9a1..c04035a 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java
@@ -16,10 +16,12 @@
package android.view.inputmethod.cts.util;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.app.Instrumentation;
import android.app.UiAutomation;
+import android.graphics.Bitmap;
import android.os.SystemClock;
import androidx.annotation.NonNull;
@@ -47,12 +49,14 @@
@NonNull
private static boolean containsWatermark(@NonNull UiAutomation uiAutomation) {
- return Watermark.detect(uiAutomation.takeScreenshot());
+ final Bitmap screenshot = uiAutomation.takeScreenshot();
+ assertNotNull(screenshot);
+ return Watermark.detect(screenshot);
}
@NonNull
private static boolean notContainsWatermark(@NonNull UiAutomation uiAutomation) {
- return !Watermark.detect(uiAutomation.takeScreenshot());
+ return !containsWatermark(uiAutomation);
}
private static boolean waitUntil(long timeout, @NonNull Predicate<UiAutomation> condition) {
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/SimulatedVirtualDisplaySession.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/SimulatedVirtualDisplaySession.java
new file mode 100644
index 0000000..84e9d18
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/SimulatedVirtualDisplaySession.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts.util;
+
+import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_PRESENTATION;
+import static android.provider.Settings.Global.OVERLAY_DISPLAY_DEVICES;
+import static android.view.Display.FLAG_TRUSTED;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.util.Size;
+import android.view.Display;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.CommonTestUtils;
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.util.StringJoiner;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A session to create/close the simulated overlay display for testing multi-display behavior.
+ */
+public final class SimulatedVirtualDisplaySession implements AutoCloseable {
+
+ private static final String OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS =
+ "should_show_system_decorations";
+
+ private final Display mDisplay;
+ private SimulatedVirtualDisplaySession(@NonNull Display display) {
+ mDisplay = display;
+ }
+
+ public static SimulatedVirtualDisplaySession create(@NonNull Context context,
+ int width, int height, int density, int displayImePolicy) {
+ final String displaySettingsStr = new StringJoiner(",")
+ // Display size/density configuration
+ .add(new Size(width, height) + "/" + density)
+ // Support system decoration to show IME on the simulated display by default
+ .add(OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS)
+ .toString();
+ putGlobalSetting(OVERLAY_DISPLAY_DEVICES, displaySettingsStr);
+
+ final DisplayManager dm = context.getSystemService(DisplayManager.class);
+ final WindowManager wm = context.getSystemService(WindowManager.class);
+ AtomicReference<Display> simulatedDisplay = new AtomicReference();
+
+ try {
+ CommonTestUtils.waitUntil("No simulated display found", 5,
+ () -> {
+ final Display[] displays = dm.getDisplays(DISPLAY_CATEGORY_PRESENTATION);
+ for (Display display : displays) {
+ final boolean isTrusted =
+ (display.getFlags() & FLAG_TRUSTED) == FLAG_TRUSTED;
+ if (isTrusted && display.getType() == Display.TYPE_OVERLAY) {
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ wm.setDisplayImePolicy(display.getDisplayId(),
+ displayImePolicy);
+ simulatedDisplay.set(display);
+ });
+ return true;
+ }
+ }
+ return false;
+ });
+ } catch (AssertionError | Exception e) {
+ deleteGlobalSetting(OVERLAY_DISPLAY_DEVICES);
+ throw new RuntimeException("Exception!", e);
+ }
+ return new SimulatedVirtualDisplaySession(simulatedDisplay.get());
+ }
+
+ private static void putGlobalSetting(String key, String value) {
+ runShellCommand("settings put global " + key + " " + value);
+ }
+
+ private static void deleteGlobalSetting(String key) {
+ runShellCommand("settings delete global " + key);
+ }
+
+ public int getDisplayId() {
+ return mDisplay.getDisplayId();
+ }
+
+ @Override
+ public void close() throws Exception {
+ deleteGlobalSetting(OVERLAY_DISPLAY_DEVICES);
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
index 398e7b6..66ef1e2 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
@@ -16,26 +16,36 @@
package android.view.inputmethod.cts.util;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Context;
import android.content.Intent;
+import android.graphics.PixelFormat;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
+import android.widget.TextView;
import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.compatibility.common.util.SystemUtil;
+
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
public class TestActivity extends Activity {
+ public static final String OVERLAY_WINDOW_NAME = "TestActivity.APP_OVERLAY_WINDOW";
private static final AtomicReference<Function<TestActivity, View>> sInitializer =
new AtomicReference<>();
@@ -45,6 +55,8 @@
private long mOnBackPressedCallCount;
+ private TextView mOverlayView;
+
/**
* Controls how {@link #onBackPressed()} behaves.
*
@@ -82,6 +94,16 @@
setContentView(mInitializer.apply(this));
}
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mOverlayView != null) {
+ mOverlayView.getContext()
+ .getSystemService(WindowManager.class).removeView(mOverlayView);
+ mOverlayView = null;
+ }
+ }
+
/**
* {@inheritDoc}
*/
@@ -94,6 +116,23 @@
super.onBackPressed();
}
+ public void showOverlayWindow() {
+ if (mOverlayView != null) {
+ throw new IllegalStateException("can only show one overlay at a time.");
+ }
+ Context overlayContext = getApplicationContext().createWindowContext(getDisplay(),
+ TYPE_APPLICATION_OVERLAY, null);
+ mOverlayView = new TextView(overlayContext);
+ WindowManager.LayoutParams params =
+ new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT,
+ TYPE_APPLICATION_OVERLAY, FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT);
+ params.setTitle(OVERLAY_WINDOW_NAME);
+ mOverlayView.setLayoutParams(params);
+ mOverlayView.setText("IME CTS TestActivity OverlayView");
+ mOverlayView.setBackgroundColor(0x77FFFF00);
+ overlayContext.getSystemService(WindowManager.class).addView(mOverlayView, params);
+ }
+
/**
* Launches {@link TestActivity} with the given initialization logic for content view.
*
@@ -111,6 +150,30 @@
}
/**
+ * Similar to {@link TestActivity#startSync(Function)}, but with the given display ID to
+ * specify the launching target display.
+ * @param displayId The ID of the display
+ * @param activityInitializer initializer to supply {@link View} to be passed to
+ * {@link Activity#setContentView(View)}
+ * @return {@link TestActivity} launched
+ */
+ public static TestActivity startSync(int displayId,
+ @NonNull Function<TestActivity, View> activityInitializer) throws Exception {
+ sInitializer.set(activityInitializer);
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(displayId);
+ final Intent intent = new Intent()
+ .setAction(Intent.ACTION_MAIN)
+ .setClass(InstrumentationRegistry.getInstrumentation().getContext(),
+ TestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ return SystemUtil.callWithShellPermissionIdentity(
+ () -> (TestActivity) InstrumentationRegistry.getInstrumentation().startActivitySync(
+ intent, options.toBundle()));
+ }
+
+ /**
* Launches {@link TestActivity} with the given initialization logic for content view.
*
* <p>As long as you are using {@link androidx.test.runner.AndroidJUnitRunner}, the test
diff --git a/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java b/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
index 207c440..3963132 100644
--- a/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
+++ b/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
@@ -77,7 +77,8 @@
}
return uri.getBooleanQueryParameter(key, false);
}
- return getIntent().getBooleanExtra(key, false);
+ return getIntent().getBooleanExtra(key, false)
+ || "true".equals(getIntent().getStringExtra(key));
}
@Override
@@ -141,7 +142,8 @@
}
}
};
- registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_TRIGGER));
+ registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_TRIGGER),
+ Context.RECEIVER_EXPORTED);
}
@Override
diff --git a/tests/leanbackjank/app/src/android/leanbackjank/app/Utils.java b/tests/leanbackjank/app/src/android/leanbackjank/app/Utils.java
deleted file mode 100644
index 1926c0d..0000000
--- a/tests/leanbackjank/app/src/android/leanbackjank/app/Utils.java
+++ /dev/null
@@ -1,114 +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.leanbackjank.app;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-
-import android.app.Activity;
-import android.content.Context;
-import android.graphics.Point;
-import android.media.MediaMetadataRetriever;
-import android.os.Build;
-import android.util.DisplayMetrics;
-import android.view.Display;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-import android.widget.Toast;
-import android.widget.VideoView;
-
-import java.util.HashMap;
-
-/**
- * A collection of utility methods, all static.
- */
-public class Utils {
-
- public interface MediaDimensions {
- double MEDIA_HEIGHT = 0.95;
- double MEDIA_WIDTH = 0.95;
- double MEDIA_TOP_MARGIN = 0.025;
- double MEDIA_RIGHT_MARGIN = 0.025;
- double MEDIA_BOTTOM_MARGIN = 0.025;
- double MEDIA_LEFT_MARGIN = 0.025;
- }
-
- /*
- * Making sure public utility methods remain static
- */
- private Utils() {
- }
-
- /**
- * Returns the screen/display size
- */
- public static Point getDisplaySize(Context context) {
- WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- Display display = wm.getDefaultDisplay();
- Point size = new Point();
- display.getSize(size);
- return size;
- }
-
- /**
- * Shows a (long) toast
- */
- public static void showToast(Context context, String msg) {
- Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
- }
-
- /**
- * Shows a (long) toast.
- */
- public static void showToast(Context context, int resourceId) {
- Toast.makeText(context, context.getString(resourceId), Toast.LENGTH_LONG).show();
- }
-
- public static int convertDpToPixel(Context ctx, int dp) {
- float density = ctx.getResources().getDisplayMetrics().density;
- return Math.round((float) dp * density);
- }
-
-
- /**
- * Example for handling resizing content for overscan. Typically you won't need to resize
- * when using the Leanback support library.
- */
- public void overScan(Activity activity, VideoView videoView) {
- DisplayMetrics metrics = new DisplayMetrics();
- activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
- int w = (int) (metrics.widthPixels * MediaDimensions.MEDIA_WIDTH);
- int h = (int) (metrics.heightPixels * MediaDimensions.MEDIA_HEIGHT);
- int marginLeft = (int) (metrics.widthPixels * MediaDimensions.MEDIA_LEFT_MARGIN);
- int marginTop = (int) (metrics.heightPixels * MediaDimensions.MEDIA_TOP_MARGIN);
- int marginRight = (int) (metrics.widthPixels * MediaDimensions.MEDIA_RIGHT_MARGIN);
- int marginBottom = (int) (metrics.heightPixels * MediaDimensions.MEDIA_BOTTOM_MARGIN);
- FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(w, h);
- lp.setMargins(marginLeft, marginTop, marginRight, marginBottom);
- videoView.setLayoutParams(lp);
- }
-
-
- public static long getDuration(String videoUrl) {
- MediaMetadataRetriever mmr = new MediaMetadataRetriever();
- if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.ICE_CREAM_SANDWICH)) {
- mmr.setDataSource(videoUrl, new HashMap<String, String>());
- } else {
- mmr.setDataSource(videoUrl);
- }
- return Long.parseLong(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
- }
-}
diff --git a/tests/libcore/jsr166/Android.bp b/tests/libcore/jsr166/Android.bp
index 1addbfd..5a84d94 100644
--- a/tests/libcore/jsr166/Android.bp
+++ b/tests/libcore/jsr166/Android.bp
@@ -33,6 +33,8 @@
optimize: {
enabled: false,
},
+ min_sdk_version: "31",
+ target_sdk_version: "31",
// Tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/tests/libcore/luni/Android.bp b/tests/libcore/luni/Android.bp
index 2fc1f56..f7ef2e7 100644
--- a/tests/libcore/luni/Android.bp
+++ b/tests/libcore/luni/Android.bp
@@ -58,6 +58,8 @@
compile_multilib: "both",
// This test requires cts-dalvik-host-test-runner to be built to run via Atest.
host_required: ["cts-dalvik-host-test-runner"],
+ min_sdk_version: "31",
+ target_sdk_version: "31",
// Tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/tests/libcore/ojluni/Android.bp b/tests/libcore/ojluni/Android.bp
index 1276621..5a9691b 100644
--- a/tests/libcore/ojluni/Android.bp
+++ b/tests/libcore/ojluni/Android.bp
@@ -37,6 +37,8 @@
// Include both the 32 and 64 bit versions of libjavacoretests,
// where applicable.
compile_multilib: "both",
+ min_sdk_version: "31",
+ target_sdk_version: "31",
// Tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/tests/libcore/runner/Android.bp b/tests/libcore/runner/Android.bp
index 4e0742f..12d3496 100644
--- a/tests/libcore/runner/Android.bp
+++ b/tests/libcore/runner/Android.bp
@@ -27,6 +27,8 @@
optimize: {
enabled: false,
},
+ min_sdk_version: "31",
+ target_sdk_version: "31",
// Tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/tests/libcore/wycheproof-bc/Android.bp b/tests/libcore/wycheproof-bc/Android.bp
index 9f31747..c2c58dd 100644
--- a/tests/libcore/wycheproof-bc/Android.bp
+++ b/tests/libcore/wycheproof-bc/Android.bp
@@ -39,6 +39,8 @@
// Include both the 32 and 64 bit versions of libjavacoretests,
// where applicable.
compile_multilib: "both",
+ min_sdk_version: "31",
+ target_sdk_version: "31",
// Tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/tests/location/common/src/android/location/cts/common/BroadcastCapture.java b/tests/location/common/src/android/location/cts/common/BroadcastCapture.java
index f2ea3ae..6ea51f7 100644
--- a/tests/location/common/src/android/location/cts/common/BroadcastCapture.java
+++ b/tests/location/common/src/android/location/cts/common/BroadcastCapture.java
@@ -41,7 +41,8 @@
}
protected void register(String action) {
- mContext.registerReceiver(this, new IntentFilter(action));
+ mContext.registerReceiver(this, new IntentFilter(action),
+ Context.RECEIVER_EXPORTED_UNAUDITED);
}
public Intent getNextIntent(long timeoutMs) throws InterruptedException {
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GeocoderTest.java b/tests/location/location_fine/src/android/location/cts/fine/GeocoderTest.java
index 69b1f0c..08ddca1 100644
--- a/tests/location/location_fine/src/android/location/cts/fine/GeocoderTest.java
+++ b/tests/location/location_fine/src/android/location/cts/fine/GeocoderTest.java
@@ -16,56 +16,57 @@
package android.location.cts.fine;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ResolveInfoFlags;
import android.location.Geocoder;
+import android.location.Geocoder.GeocodeListener;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.compatibility.common.util.RetryRule;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.IOException;
import java.util.Locale;
@RunWith(AndroidJUnit4.class)
public class GeocoderTest {
- private static final int MAX_NUM_RETRIES = 2;
- private static final int TIME_BETWEEN_RETRIES_MS = 2000;
+ // retry just in case of network failure
+ @Rule
+ public final RetryRule mRetryRule = new RetryRule(2);
private Context mContext;
+ private Geocoder mGeocoder;
@Before
public void setUp() {
mContext = ApplicationProvider.getApplicationContext();
- }
-
- @Test
- public void testConstructor() {
- new Geocoder(mContext);
-
- new Geocoder(mContext, Locale.ENGLISH);
-
- try {
- new Geocoder(mContext, null);
- fail("should throw NullPointerException.");
- } catch (NullPointerException e) {
- // expected.
- }
+ mGeocoder = new Geocoder(mContext, Locale.US);
}
@Test
public void testIsPresent() {
- if (isServiceMissing()) {
+ if (mContext.getPackageManager().queryIntentServices(
+ new Intent("com.android.location.service.GeocodeProvider"), ResolveInfoFlags.of(
+ MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE)).isEmpty()) {
assertFalse(Geocoder.isPresent());
} else {
assertTrue(Geocoder.isPresent());
@@ -73,129 +74,62 @@
}
@Test
- public void testGetFromLocation() throws IOException, InterruptedException {
- Geocoder geocoder = new Geocoder(mContext);
+ public void testGetFromLocation() {
+ assumeTrue(Geocoder.isPresent());
- // There is no guarantee that geocoder.getFromLocation returns accurate results
- // Thus only test that calling the method with valid arguments doesn't produce
- // an unexpected exception
- // Note: there is a risk this test will fail if device under test does not have
- // a network connection. This is why we try the geocode 5 times if it fails due
- // to a network error.
- int numRetries = 0;
- while (numRetries < MAX_NUM_RETRIES) {
- try {
- geocoder.getFromLocation(60, 30, 5);
- break;
- } catch (IOException e) {
- Thread.sleep(TIME_BETWEEN_RETRIES_MS);
- numRetries++;
- }
- }
- if (numRetries >= MAX_NUM_RETRIES) {
- fail("Failed to geocode location " + MAX_NUM_RETRIES + " times.");
- }
-
-
- try {
- // latitude is less than -90
- geocoder.getFromLocation(-91, 30, 5);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // pass
- }
-
- try {
- // latitude is greater than 90
- geocoder.getFromLocation(91, 30, 5);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // pass
- }
-
- try {
- // longitude is less than -180
- geocoder.getFromLocation(10, -181, 5);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // pass
- }
-
- try {
- // longitude is greater than 180
- geocoder.getFromLocation(10, 181, 5);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // pass
- }
+ GeocodeListener listener = mock(GeocodeListener.class);
+ mGeocoder.getFromLocation(60, 30, 5, listener);
+ verify(listener, timeout(10000)).onGeocode(anyList());
}
@Test
- public void testGetFromLocationName() throws IOException, InterruptedException {
- Geocoder geocoder = new Geocoder(mContext, Locale.US);
+ public void testGetFromLocation_sync() throws Exception {
+ assumeTrue(Geocoder.isPresent());
- // There is no guarantee that geocoder.getFromLocationName returns accurate results.
- // Thus only test that calling the method with valid arguments doesn't produce
- // an unexpected exception
- // Note: there is a risk this test will fail if device under test does not have
- // a network connection. This is why we try the geocode 5 times if it fails due
- // to a network error.
- int numRetries = 0;
- while (numRetries < MAX_NUM_RETRIES) {
- try {
- geocoder.getFromLocationName("Dalvik,Iceland", 5);
- break;
- } catch (IOException e) {
- Thread.sleep(TIME_BETWEEN_RETRIES_MS);
- numRetries++;
- }
- }
- if (numRetries >= MAX_NUM_RETRIES) {
- fail("Failed to geocode location name " + MAX_NUM_RETRIES + " times.");
- }
-
- try {
- geocoder.getFromLocationName(null, 5);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // pass
- }
-
- try {
- geocoder.getFromLocationName("Beijing", 5, -91, 100, 45, 130);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // pass
- }
-
- try {
- geocoder.getFromLocationName("Beijing", 5, 25, 190, 45, 130);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // pass
- }
-
- try {
- geocoder.getFromLocationName("Beijing", 5, 25, 100, 91, 130);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // pass
- }
-
- try {
- geocoder.getFromLocationName("Beijing", 5, 25, 100, 45, -181);
- fail("should throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // pass
- }
+ mGeocoder.getFromLocation(60, 30, 5);
}
- private boolean isServiceMissing() {
- PackageManager pm = mContext.getPackageManager();
+ @Test
+ public void testGetFromLocation_badInput() {
+ GeocodeListener listener = mock(GeocodeListener.class);
+ assertThrows(IllegalArgumentException.class,
+ () -> mGeocoder.getFromLocation(-91, 30, 5, listener));
+ assertThrows(IllegalArgumentException.class,
+ () -> mGeocoder.getFromLocation(91, 30, 5, listener));
+ assertThrows(IllegalArgumentException.class,
+ () -> mGeocoder.getFromLocation(10, -181, 5, listener));
+ assertThrows(IllegalArgumentException.class,
+ () -> mGeocoder.getFromLocation(10, 181, 5, listener));
+ }
- final Intent intent = new Intent("com.android.location.service.GeocodeProvider");
- final int flags = PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- return pm.queryIntentServices(intent, flags).isEmpty();
+ @Test
+ public void testGetFromLocationName() {
+ assumeTrue(Geocoder.isPresent());
+
+ GeocodeListener listener = mock(GeocodeListener.class);
+ mGeocoder.getFromLocationName("Dalvik,Iceland", 5, listener);
+ verify(listener, timeout(10000)).onGeocode(anyList());
+ }
+
+ @Test
+ public void testGetFromLocationName_sync() throws Exception {
+ assumeTrue(Geocoder.isPresent());
+
+ mGeocoder.getFromLocationName("Dalvik,Iceland", 5);
+ }
+
+ @Test
+ public void testGetFromLocationName_badInput() {
+ GeocodeListener listener = mock(GeocodeListener.class);
+ assertThrows(IllegalArgumentException.class,
+ () -> mGeocoder.getFromLocationName(null, 5, listener));
+ assertThrows(IllegalArgumentException.class,
+ () -> mGeocoder.getFromLocationName("Beijing", 5, -91, 100, 45, 130, listener));
+ assertThrows(IllegalArgumentException.class,
+ () -> mGeocoder.getFromLocationName("Beijing", 5, 25, 190, 45, 130, listener));
+ assertThrows(IllegalArgumentException.class,
+ () -> mGeocoder.getFromLocationName("Beijing", 5, 25, 100, 91, 130, listener));
+ assertThrows(IllegalArgumentException.class,
+ () -> mGeocoder.getFromLocationName("Beijing", 5, 25, 100, 45, -181, listener));
}
}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java b/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java
index 6d0ac08..af9ecff 100644
--- a/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java
+++ b/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java
@@ -155,6 +155,9 @@
// Scrolling can fail if the UI is not scrollable
}
+ // Wait for the preference to appear
+ mDevice.wait(Until.hasObject(By.text(res.getString(resId))), TIMEOUT);
+
UiObject2 pref = mDevice.findObject(By.text(res.getString(resId)));
// Click the preference to show the Scanning fragment
pref.click();
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
index f615d51..320cf62 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
@@ -16,11 +16,14 @@
package android.location.cts.gnss;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
import android.location.GnssMeasurement;
import android.location.GnssMeasurementRequest;
import android.location.GnssMeasurementsEvent;
import android.location.GnssStatus;
-import android.location.cts.common.GnssTestCase;
import android.location.cts.common.SoftAssert;
import android.location.cts.common.TestGnssMeasurementListener;
import android.location.cts.common.TestLocationListener;
@@ -28,6 +31,14 @@
import android.location.cts.common.TestMeasurementUtil;
import android.util.Log;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
import java.util.List;
/**
@@ -50,23 +61,25 @@
* Android Q, it is mandatory to report GnssMeasurement even if a location has not
* yet been reported. Therefore, the test fails.
*/
-public class GnssMeasurementRegistrationTest extends GnssTestCase {
+@RunWith(JUnit4.class)
+public class GnssMeasurementRegistrationTest {
private static final String TAG = "GnssMeasRegTest";
private static final int EVENTS_COUNT = 5;
private static final int GPS_EVENTS_COUNT = 1;
private TestLocationListener mLocationListener;
private TestGnssMeasurementListener mMeasurementListener;
+ private TestLocationManager mTestLocationManager;
+ private Context mContext;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mTestLocationManager = new TestLocationManager(getContext());
+ @Before
+ public void setUp() throws Exception {
+ mContext = ApplicationProvider.getApplicationContext();
+ mTestLocationManager = new TestLocationManager(mContext);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
// Unregister listeners
if (mLocationListener != null) {
mTestLocationManager.removeLocationUpdates(mLocationListener);
@@ -74,22 +87,16 @@
if (mMeasurementListener != null) {
mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
}
- super.tearDown();
}
/**
* Test GPS measurements registration.
*/
+ @Test
public void testGnssMeasurementRegistration() throws Exception {
- // Checks if GPS hardware feature is present, skips test (pass) if not
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
- return;
- }
-
- if (TestMeasurementUtil.isAutomotiveDevice(getContext())) {
- Log.i(TAG, "Test is being skipped because the system has the AUTOMOTIVE feature.");
- return;
- }
+ assumeTrue(TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG));
+ assumeFalse("Test is being skipped because the system has the AUTOMOTIVE feature.",
+ TestMeasurementUtil.isAutomotiveDevice(mContext));
// Register for GPS measurements.
mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
@@ -101,16 +108,11 @@
/**
* Test GPS measurements registration with full tracking enabled.
*/
+ @Test
public void testGnssMeasurementRegistration_enableFullTracking() throws Exception {
- // Checks if GPS hardware feature is present, skips test (pass) if not,
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
- return;
- }
-
- if (TestMeasurementUtil.isAutomotiveDevice(getContext())) {
- Log.i(TAG, "Test is being skipped because the system has the AUTOMOTIVE feature.");
- return;
- }
+ assumeTrue(TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG));
+ assumeFalse("Test is being skipped because the system has the AUTOMOTIVE feature.",
+ TestMeasurementUtil.isAutomotiveDevice(mContext));
// Register for GPS measurements.
mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
@@ -120,6 +122,23 @@
verifyGnssMeasurementsReceived();
}
+ /**
+ * Test GPS measurements registration with 2s interval.
+ */
+ @Test
+ public void testGnssMeasurementRegistration_2secInterval() throws Exception {
+ assumeTrue(TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG));
+ assumeFalse("Test is being skipped because the system has the AUTOMOTIVE feature.",
+ TestMeasurementUtil.isAutomotiveDevice(mContext));
+
+ // Register for GPS measurements.
+ mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
+ mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener,
+ new GnssMeasurementRequest.Builder().setIntervalMillis(2000).build());
+
+ verifyGnssMeasurementsReceived();
+ }
+
private void verifyGnssMeasurementsReceived() throws InterruptedException {
mMeasurementListener.await();
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssAntennaInfoTest.java b/tests/location/location_none/src/android/location/cts/none/GnssAntennaInfoTest.java
index 584a26e..640ecf6 100644
--- a/tests/location/location_none/src/android/location/cts/none/GnssAntennaInfoTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/GnssAntennaInfoTest.java
@@ -118,6 +118,24 @@
verifyPartialGnssAntennaInfoValuesAndGetters(gnssAntennaInfo);
}
+ @Test
+ public void testGnssAntennaInfoCopyBuilder() {
+ GnssAntennaInfo gnssAntennaInfo = createFullTestGnssAntennaInfo();
+ GnssAntennaInfo gnssAntennaInfoCopy = new GnssAntennaInfo.Builder(gnssAntennaInfo).build();
+
+ verifyFullGnssAntennaInfoValuesAndGetters(gnssAntennaInfoCopy);
+ }
+
+ @Test
+ public void testGnssAntennaInfoBuilderWithArguments() {
+ GnssAntennaInfo gnssAntennaInfo = createPartialTestGnssAntennaInfo();
+ GnssAntennaInfo gnssAntennaInfoCopy = new GnssAntennaInfo.Builder(
+ gnssAntennaInfo.getCarrierFrequencyMHz(),
+ gnssAntennaInfo.getPhaseCenterOffset()).build();
+
+ verifyPartialGnssAntennaInfoValuesAndGetters(gnssAntennaInfoCopy);
+ }
+
private static GnssAntennaInfo createFullTestGnssAntennaInfo() {
double carrierFrequencyMHz = 13758.0;
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssAutomaticGainControlTest.java b/tests/location/location_none/src/android/location/cts/none/GnssAutomaticGainControlTest.java
new file mode 100644
index 0000000..8e8f112
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/GnssAutomaticGainControlTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.none;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.location.GnssAutomaticGainControl;
+import android.location.GnssStatus;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests fundamental functionality of {@link GnssAutomaticGainControl} class. This includes writing
+ * and reading from parcel, and verifying computed values and getters.
+ */
+@RunWith(AndroidJUnit4.class)
+public class GnssAutomaticGainControlTest {
+ private static final float DELTA = 1e-3f;
+
+ @Test
+ public void testGetValues() {
+ GnssAutomaticGainControl agc = new GnssAutomaticGainControl.Builder().setCarrierFrequencyHz(
+ 1575420000).setConstellationType(GnssStatus.CONSTELLATION_GPS).setLevelDb(
+ 3.5).build();
+ assertEquals(GnssStatus.CONSTELLATION_GPS, agc.getConstellationType());
+ assertEquals(1575420000, agc.getCarrierFrequencyHz());
+ assertEquals(3.5, agc.getLevelDb(), DELTA);
+ }
+
+ @Test
+ public void testDescribeContents() {
+ GnssAutomaticGainControl agc = new GnssAutomaticGainControl.Builder().build();
+ assertEquals(agc.describeContents(), 0);
+ }
+
+ @Test
+ public void testWriteToParcel() {
+ GnssAutomaticGainControl agc = new GnssAutomaticGainControl.Builder().setCarrierFrequencyHz(
+ 1575420000).setConstellationType(GnssStatus.CONSTELLATION_GPS).setLevelDb(
+ 3.5).build();
+
+ Parcel parcel = Parcel.obtain();
+ agc.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ GnssAutomaticGainControl fromParcel = GnssAutomaticGainControl.CREATOR.createFromParcel(
+ parcel);
+
+ assertEquals(agc, fromParcel);
+ }
+
+ @Test
+ public void testEquals() {
+ GnssAutomaticGainControl agc1 =
+ new GnssAutomaticGainControl.Builder().setCarrierFrequencyHz(
+ 1575420000).setConstellationType(GnssStatus.CONSTELLATION_GPS).setLevelDb(
+ 3.5).build();
+ GnssAutomaticGainControl agc2 = new GnssAutomaticGainControl.Builder(agc1).build();
+ GnssAutomaticGainControl agc3 =
+ new GnssAutomaticGainControl.Builder().setCarrierFrequencyHz(
+ 1602000000).setConstellationType(
+ GnssStatus.CONSTELLATION_GLONASS).setLevelDb(-2.8).build();
+
+ assertEquals(agc1, agc2);
+ assertNotEquals(agc1, agc3);
+ }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java
index feb44d8..2971052 100644
--- a/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java
@@ -1,5 +1,7 @@
package android.location.cts.none;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -19,30 +21,33 @@
*/
@RunWith(AndroidJUnit4.class)
public class GnssMeasurementRequestTest {
-
- private GnssMeasurementRequest getTestGnssMeasurementRequest(boolean fullTracking) {
- GnssMeasurementRequest.Builder builder = new GnssMeasurementRequest.Builder();
- builder.setFullTracking(fullTracking);
- return builder.build();
- }
+ private static final int TEST_INTERVAL_MS = 2000;
@Test
public void testGetValues() {
- GnssMeasurementRequest request1 = getTestGnssMeasurementRequest(true);
+ GnssMeasurementRequest request1 =
+ new GnssMeasurementRequest.Builder().setFullTracking(true).build();
assertTrue(request1.isFullTracking());
- GnssMeasurementRequest request2 = getTestGnssMeasurementRequest(false);
+ GnssMeasurementRequest request2 =
+ new GnssMeasurementRequest.Builder().setFullTracking(false).build();
assertFalse(request2.isFullTracking());
+
+ GnssMeasurementRequest request3 =
+ new GnssMeasurementRequest.Builder().setIntervalMillis(TEST_INTERVAL_MS).build();
+ assertThat(request3.getIntervalMillis()).isEqualTo(TEST_INTERVAL_MS);
}
@Test
public void testDescribeContents() {
- GnssMeasurementRequest request = getTestGnssMeasurementRequest(true);
+ GnssMeasurementRequest request =
+ new GnssMeasurementRequest.Builder().setFullTracking(true).build();
assertEquals(request.describeContents(), 0);
}
@Test
public void testWriteToParcel() {
- GnssMeasurementRequest request = getTestGnssMeasurementRequest(true);
+ GnssMeasurementRequest request =
+ new GnssMeasurementRequest.Builder().setFullTracking(true).build();
Parcel parcel = Parcel.obtain();
request.writeToParcel(parcel, 0);
@@ -54,10 +59,15 @@
@Test
public void testEquals() {
- GnssMeasurementRequest request1 = getTestGnssMeasurementRequest(true);
+ GnssMeasurementRequest request1 =
+ new GnssMeasurementRequest.Builder().setFullTracking(true).build();
GnssMeasurementRequest request2 = new GnssMeasurementRequest.Builder(request1).build();
- GnssMeasurementRequest request3 = getTestGnssMeasurementRequest(false);
+ GnssMeasurementRequest request3 =
+ new GnssMeasurementRequest.Builder().setFullTracking(false).build();
+ GnssMeasurementRequest request4 =
+ new GnssMeasurementRequest.Builder().setIntervalMillis(TEST_INTERVAL_MS).build();
assertEquals(request1, request2);
assertNotEquals(request3, request2);
+ assertNotEquals(request4, request3);
}
}
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssMeasurementsEventTest.java b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementsEventTest.java
index dc1c56f..870bb5d 100644
--- a/tests/location/location_none/src/android/location/cts/none/GnssMeasurementsEventTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementsEventTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
+import android.location.GnssAutomaticGainControl;
import android.location.GnssClock;
import android.location.GnssMeasurement;
import android.location.GnssMeasurementsEvent;
@@ -31,6 +32,7 @@
import java.util.Collection;
import java.util.Iterator;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
public class GnssMeasurementsEventTest {
@@ -40,8 +42,10 @@
GnssClock clock = new GnssClock();
GnssMeasurement m1 = new GnssMeasurement();
GnssMeasurement m2 = new GnssMeasurement();
- GnssMeasurementsEvent event = new GnssMeasurementsEvent(
- clock, new GnssMeasurement[] {m1, m2});
+ GnssAutomaticGainControl agc = new GnssAutomaticGainControl.Builder().build();
+ GnssMeasurementsEvent event = new GnssMeasurementsEvent.Builder().setClock(clock)
+ .setMeasurements(List.of(m1, m2))
+ .setGnssAutomaticGainControls(List.of(agc)).build();
assertEquals(0, event.describeContents());
}
@@ -53,12 +57,22 @@
m1.setConstellationType(GnssStatus.CONSTELLATION_GLONASS);
GnssMeasurement m2 = new GnssMeasurement();
m2.setReceivedSvTimeNanos(43999);
- GnssMeasurementsEvent event = new GnssMeasurementsEvent(
- clock, new GnssMeasurement[] {m1, m2});
+ GnssAutomaticGainControl agc1 =
+ new GnssAutomaticGainControl.Builder().setCarrierFrequencyHz(
+ 1575420000).setConstellationType(
+ GnssStatus.CONSTELLATION_GPS).setLevelDb(3.5).build();
+ GnssAutomaticGainControl agc2 =
+ new GnssAutomaticGainControl.Builder().setCarrierFrequencyHz(
+ 1602000000).setConstellationType(
+ GnssStatus.CONSTELLATION_GLONASS).setLevelDb(-2.8).build();
+ GnssMeasurementsEvent event = new GnssMeasurementsEvent.Builder().setClock(clock)
+ .setMeasurements(List.of(m1, m2))
+ .setGnssAutomaticGainControls(List.of(agc1, agc2)).build();
Parcel parcel = Parcel.obtain();
event.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
GnssMeasurementsEvent newEvent = GnssMeasurementsEvent.CREATOR.createFromParcel(parcel);
+
assertEquals(100, newEvent.getClock().getLeapSecond());
Collection<GnssMeasurement> measurements = newEvent.getMeasurements();
assertEquals(2, measurements.size());
@@ -67,5 +81,13 @@
assertEquals(GnssStatus.CONSTELLATION_GLONASS, newM1.getConstellationType());
GnssMeasurement newM2 = iterator.next();
assertEquals(43999, newM2.getReceivedSvTimeNanos());
+
+ Collection<GnssAutomaticGainControl> agcs = newEvent.getGnssAutomaticGainControls();
+ assertEquals(2, agcs.size());
+ Iterator<GnssAutomaticGainControl> gnssAgcIterator = agcs.iterator();
+ GnssAutomaticGainControl newAgc1 = gnssAgcIterator.next();
+ assertEquals(newAgc1, agc1);
+ GnssAutomaticGainControl newAgc2 = gnssAgcIterator.next();
+ assertEquals(newAgc2, agc2);
}
}
diff --git a/tests/location/location_none/src/android/location/cts/none/LastLocationRequestTest.java b/tests/location/location_none/src/android/location/cts/none/LastLocationRequestTest.java
index 670b536..ed2c11c 100644
--- a/tests/location/location_none/src/android/location/cts/none/LastLocationRequestTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/LastLocationRequestTest.java
@@ -33,6 +33,7 @@
public void testBuild_Defaults() {
LastLocationRequest request = new LastLocationRequest.Builder().build();
assertThat(request.isHiddenFromAppOps()).isEqualTo(false);
+ assertThat(request.isAdasGnssBypass()).isEqualTo(false);
assertThat(request.isLocationSettingsIgnored()).isEqualTo(false);
}
@@ -40,9 +41,11 @@
public void testBuild_Explicit() {
LastLocationRequest request = new LastLocationRequest.Builder()
.setHiddenFromAppOps(true)
+ .setAdasGnssBypass(true)
.setLocationSettingsIgnored(true)
.build();
assertThat(request.isHiddenFromAppOps()).isEqualTo(true);
+ assertThat(request.isAdasGnssBypass()).isEqualTo(true);
assertThat(request.isLocationSettingsIgnored()).isEqualTo(true);
}
@@ -50,10 +53,12 @@
public void testBuild_Copy() {
LastLocationRequest original = new LastLocationRequest.Builder()
.setHiddenFromAppOps(true)
+ .setAdasGnssBypass(true)
.setLocationSettingsIgnored(true)
.build();
LastLocationRequest copy = new LastLocationRequest.Builder(original).build();
assertThat(copy.isHiddenFromAppOps()).isEqualTo(true);
+ assertThat(copy.isAdasGnssBypass()).isEqualTo(true);
assertThat(copy.isLocationSettingsIgnored()).isEqualTo(true);
assertThat(copy).isEqualTo(original);
}
@@ -68,6 +73,7 @@
public void testParcelRoundtrip() {
LastLocationRequest request = new LastLocationRequest.Builder()
.setHiddenFromAppOps(true)
+ .setAdasGnssBypass(true)
.setLocationSettingsIgnored(true)
.build();
diff --git a/tests/location/location_none/src/android/location/cts/none/LocationRequestTest.java b/tests/location/location_none/src/android/location/cts/none/LocationRequestTest.java
index 97b57af..8f6dd3f 100644
--- a/tests/location/location_none/src/android/location/cts/none/LocationRequestTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/LocationRequestTest.java
@@ -43,6 +43,7 @@
assertThat(request.getMaxUpdates()).isEqualTo(Integer.MAX_VALUE);
assertThat(request.getMinUpdateDistanceMeters()).isEqualTo(0f);
assertThat(request.isHiddenFromAppOps()).isEqualTo(false);
+ assertThat(request.isAdasGnssBypass()).isEqualTo(false);
assertThat(request.isLocationSettingsIgnored()).isEqualTo(false);
assertThat(request.isLowPower()).isEqualTo(false);
}
@@ -56,6 +57,7 @@
.setMaxUpdates(7000)
.setMinUpdateDistanceMeters(8000f)
.setHiddenFromAppOps(true)
+ .setAdasGnssBypass(true)
.setLocationSettingsIgnored(true)
.setLowPower(true)
.build();
@@ -66,6 +68,7 @@
assertThat(request.getMaxUpdates()).isEqualTo(7000);
assertThat(request.getMinUpdateDistanceMeters()).isEqualTo(8000f);
assertThat(request.isHiddenFromAppOps()).isEqualTo(true);
+ assertThat(request.isAdasGnssBypass()).isEqualTo(true);
assertThat(request.isLocationSettingsIgnored()).isEqualTo(true);
assertThat(request.isLowPower()).isEqualTo(true);
}
@@ -79,6 +82,7 @@
.setMaxUpdates(7000)
.setMinUpdateDistanceMeters(8000f)
.setHiddenFromAppOps(true)
+ .setAdasGnssBypass(true)
.setLocationSettingsIgnored(true)
.setLowPower(true)
.build();
@@ -90,6 +94,7 @@
assertThat(copy.getMaxUpdates()).isEqualTo(7000);
assertThat(copy.getMinUpdateDistanceMeters()).isEqualTo(8000f);
assertThat(copy.isHiddenFromAppOps()).isEqualTo(true);
+ assertThat(copy.isAdasGnssBypass()).isEqualTo(true);
assertThat(copy.isLocationSettingsIgnored()).isEqualTo(true);
assertThat(copy.isLowPower()).isEqualTo(true);
assertThat(copy).isEqualTo(original);
@@ -178,6 +183,7 @@
.setMaxUpdates(7000)
.setMinUpdateDistanceMeters(8000f)
.setHiddenFromAppOps(true)
+ .setAdasGnssBypass(true)
.setLocationSettingsIgnored(true)
.setLowPower(true)
.build();
diff --git a/tests/location/location_none/src/android/location/cts/none/LocationTest.java b/tests/location/location_none/src/android/location/cts/none/LocationTest.java
index 12b93046..87a15b8 100644
--- a/tests/location/location_none/src/android/location/cts/none/LocationTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/LocationTest.java
@@ -16,11 +16,10 @@
package android.location.cts.none;
+import static com.google.common.truth.Truth.assertThat;
+
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.location.Location;
@@ -38,90 +37,143 @@
@RunWith(AndroidJUnit4.class)
public class LocationTest {
- private static final float DELTA = 0.1f;
- private final float TEST_ACCURACY = 1.0f;
- private final float TEST_VERTICAL_ACCURACY = 2.0f;
- private final float TEST_SPEED_ACCURACY = 3.0f;
- private final float TEST_BEARING_ACCURACY = 4.0f;
- private final double TEST_ALTITUDE = 1.0;
- private final double TEST_LATITUDE = 50;
- private final float TEST_BEARING = 1.0f;
- private final double TEST_LONGITUDE = 20;
- private final float TEST_SPEED = 5.0f;
- private final long TEST_TIME = 100;
- private final String TEST_PROVIDER = "LocationProvider";
- private final String TEST_KEY1NAME = "key1";
- private final String TEST_KEY2NAME = "key2";
- private final boolean TEST_KEY1VALUE = false;
- private final byte TEST_KEY2VALUE = 10;
+ private static final float DELTA = 0.01f;
@Test
- public void testConstructor() {
- new Location("LocationProvider");
+ public void testConstructor_Defaults() {
+ Location l = new Location("provider");
- Location l = createTestLocation();
- Location location = new Location(l);
- assertTestLocation(location);
+ assertThat(l.getProvider()).isEqualTo("provider");
+ assertThat(l.getTime()).isEqualTo(0);
+ assertThat(l.getElapsedRealtimeNanos()).isEqualTo(0);
+ assertThat(l.hasElapsedRealtimeUncertaintyNanos()).isFalse();
+ assertThat(l.getLatitude()).isEqualTo(0);
+ assertThat(l.getLongitude()).isEqualTo(0);
+ assertThat(l.hasAltitude()).isFalse();
+ assertThat(l.hasSpeed()).isFalse();
+ assertThat(l.hasBearing()).isFalse();
+ assertThat(l.hasVerticalAccuracy()).isFalse();
+ assertThat(l.hasSpeedAccuracy()).isFalse();
+ assertThat(l.hasBearingAccuracy()).isFalse();
+ assertThat(l.isMock()).isFalse();
+ assertThat(l.getExtras()).isNull();
+ }
- try {
- new Location((Location) null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- // expected.
- }
+ @Test
+ public void testSet() {
+ Location location = new Location("");
+
+ Location l = new Location("test");
+ l.setTime(1);
+ l.setElapsedRealtimeNanos(2);
+ l.setElapsedRealtimeUncertaintyNanos(3);
+ l.setLatitude(-90);
+ l.setLongitude(90);
+ l.setAltitude(100);
+ l.setVerticalAccuracyMeters(90);
+ l.setSpeed(1000);
+ l.setSpeedAccuracyMetersPerSecond(9);
+ l.setBearing(7);
+ l.setBearingAccuracyDegrees(11);
+ l.setMock(true);
+ Bundle b = new Bundle();
+ b.putString("key", "value");
+ l.setExtras(b);
+
+ location.set(l);
+ assertThat(location).isEqualTo(l);
+ }
+
+ @Test
+ public void testValues() {
+ Location l = new Location("provider");
+
+ l.setProvider("test");
+ assertThat(l.getProvider()).isEqualTo("test");
+
+ l.setTime(1);
+ assertThat(l.getTime()).isEqualTo(1);
+ l.setTime(Long.MAX_VALUE);
+ assertThat(l.getTime()).isEqualTo(Long.MAX_VALUE);
+
+ l.setElapsedRealtimeNanos(1);
+ assertThat(l.getElapsedRealtimeNanos()).isEqualTo(1);
+ l.setElapsedRealtimeNanos(Long.MAX_VALUE);
+ assertThat(l.getElapsedRealtimeNanos()).isEqualTo(Long.MAX_VALUE);
+
+ l.setElapsedRealtimeUncertaintyNanos(1);
+ assertThat(l.hasElapsedRealtimeUncertaintyNanos()).isTrue();
+ assertThat(l.getElapsedRealtimeUncertaintyNanos()).isEqualTo(1);
+ l.removeElapsedRealtimeUncertaintyNanos();
+ assertThat(l.hasElapsedRealtimeUncertaintyNanos()).isFalse();
+
+ l.setLatitude(-90);
+ assertThat(l.getLatitude()).isEqualTo(-90);
+
+ l.setLongitude(90);
+ assertThat(l.getLongitude()).isEqualTo(90);
+
+ l.setAltitude(100);
+ assertThat(l.hasAltitude()).isTrue();
+ assertThat(l.getAltitude()).isEqualTo(100);
+ l.removeAltitude();
+ assertThat(l.hasAltitude()).isFalse();
+
+ l.setVerticalAccuracyMeters(90);
+ assertThat(l.hasVerticalAccuracy()).isTrue();
+ assertThat(l.getVerticalAccuracyMeters()).isEqualTo(90);
+ l.removeVerticalAccuracy();
+ assertThat(l.hasVerticalAccuracy()).isFalse();
+
+ l.setSpeed(1000);
+ assertThat(l.hasSpeed()).isTrue();
+ assertThat(l.getSpeed()).isEqualTo(1000);
+ l.removeSpeed();
+ assertThat(l.hasSpeed()).isFalse();
+
+ l.setSpeedAccuracyMetersPerSecond(9);
+ assertThat(l.hasSpeedAccuracy()).isTrue();
+ assertThat(l.getSpeedAccuracyMetersPerSecond()).isEqualTo(9);
+ l.removeSpeedAccuracy();
+ assertThat(l.hasSpeedAccuracy()).isFalse();
+
+ l.setBearing(7);
+ assertThat(l.hasBearing()).isTrue();
+ assertThat(l.getBearing()).isEqualTo(7);
+ l.setBearing(Float.MAX_VALUE);
+ assertThat(l.getBearing()).isEqualTo(0);
+ l.setBearing((Float.MAX_VALUE - 1) * -1);
+ assertThat(l.getBearing()).isEqualTo(0);
+ l.setBearing(371);
+ assertThat(l.getBearing()).isEqualTo(11f);
+ l.setBearing(-371);
+ assertThat(l.getBearing()).isEqualTo(349f);
+ l.removeBearing();
+ assertThat(l.hasBearing()).isFalse();
+
+ l.setBearingAccuracyDegrees(11);
+ assertThat(l.hasBearingAccuracy()).isTrue();
+ assertThat(l.getBearingAccuracyDegrees()).isEqualTo(11);
+ l.removeBearingAccuracy();
+ assertThat(l.hasBearingAccuracy()).isFalse();
+
+ l.setMock(true);
+ assertThat(l.isMock()).isTrue();
+ l.setMock(false);
+ assertThat(l.isMock()).isFalse();
+
+ l.setExtras(new Bundle());
+ assertThat(l.getExtras()).isNotNull();
}
@Test
public void testDump() {
StringBuilder sb = new StringBuilder();
- StringBuilderPrinter printer = new StringBuilderPrinter(sb);
- Location location = new Location("LocationProvider");
- location.dump(printer, "");
+ new Location("").dump(new StringBuilderPrinter(sb), "");
assertNotNull(sb.toString());
}
@Test
- public void testBearingTo() {
- Location location = new Location("");
- Location dest = new Location("");
-
- // set the location to Beijing
- location.setLatitude(39.9);
- location.setLongitude(116.4);
- // set the destination to Chengdu
- dest.setLatitude(30.7);
- dest.setLongitude(104.1);
- assertEquals(-128.66, location.bearingTo(dest), DELTA);
-
- float bearing;
- Location zeroLocation = new Location("");
- zeroLocation.setLatitude(0);
- zeroLocation.setLongitude(0);
-
- Location testLocation = new Location("");
- testLocation.setLatitude(0);
- testLocation.setLongitude(150);
-
- bearing = zeroLocation.bearingTo(zeroLocation);
- assertEquals(0.0f, bearing, DELTA);
-
- bearing = zeroLocation.bearingTo(testLocation);
- assertEquals(90.0f, bearing, DELTA);
-
- testLocation.setLatitude(90);
- testLocation.setLongitude(0);
- bearing = zeroLocation.bearingTo(testLocation);
- assertEquals(0.0f, bearing, DELTA);
-
- try {
- location.bearingTo(null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- // expected.
- }
- }
-
- @Test
public void testConvert_CoordinateToRepresentation() {
DecimalFormat df = new DecimalFormat("###.#####");
String result;
@@ -219,12 +271,6 @@
}
@Test
- public void testDescribeContents() {
- Location location = new Location("");
- location.describeContents();
- }
-
- @Test
public void testDistanceBetween() {
float[] result = new float[3];
Location.distanceBetween(0, 0, 0, 0, result);
@@ -271,278 +317,72 @@
}
@Test
- public void testAccessAccuracy() {
+ public void testBearingTo() {
Location location = new Location("");
- assertFalse(location.hasAccuracy());
+ Location dest = new Location("");
- location.setAccuracy(1.0f);
- assertEquals(1.0, location.getAccuracy(), DELTA);
- assertTrue(location.hasAccuracy());
+ // set the location to Beijing
+ location.setLatitude(39.9);
+ location.setLongitude(116.4);
+ // set the destination to Chengdu
+ dest.setLatitude(30.7);
+ dest.setLongitude(104.1);
+ assertEquals(-128.66, location.bearingTo(dest), DELTA);
+
+ float bearing;
+ Location zeroLocation = new Location("");
+ zeroLocation.setLatitude(0);
+ zeroLocation.setLongitude(0);
+
+ Location testLocation = new Location("");
+ testLocation.setLatitude(0);
+ testLocation.setLongitude(150);
+
+ bearing = zeroLocation.bearingTo(zeroLocation);
+ assertEquals(0.0f, bearing, DELTA);
+
+ bearing = zeroLocation.bearingTo(testLocation);
+ assertEquals(90.0f, bearing, DELTA);
+
+ testLocation.setLatitude(90);
+ testLocation.setLongitude(0);
+ bearing = zeroLocation.bearingTo(testLocation);
+ assertEquals(0.0f, bearing, DELTA);
+
+ try {
+ location.bearingTo(null);
+ fail("should throw NullPointerException");
+ } catch (NullPointerException e) {
+ // expected.
+ }
}
@Test
- public void testAccessVerticalAccuracy() {
- Location location = new Location("");
- assertFalse(location.hasVerticalAccuracy());
-
- location.setVerticalAccuracyMeters(1.0f);
- assertEquals(1.0, location.getVerticalAccuracyMeters(), DELTA);
- assertTrue(location.hasVerticalAccuracy());
- }
-
- @Test
- public void testAccessSpeedAccuracy() {
- Location location = new Location("");
- assertFalse(location.hasSpeedAccuracy());
-
- location.setSpeedAccuracyMetersPerSecond(1.0f);
- assertEquals(1.0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
- assertTrue(location.hasSpeedAccuracy());
- }
-
- @Test
- public void testAccessBearingAccuracy() {
- Location location = new Location("");
- assertFalse(location.hasBearingAccuracy());
-
- location.setBearingAccuracyDegrees(1.0f);
- assertEquals(1.0, location.getBearingAccuracyDegrees(), DELTA);
- assertTrue(location.hasBearingAccuracy());
- }
-
-
- @Test
- public void testAccessAltitude() {
- Location location = new Location("");
- assertFalse(location.hasAltitude());
-
- location.setAltitude(1.0);
- assertEquals(1.0, location.getAltitude(), DELTA);
- assertTrue(location.hasAltitude());
- }
-
- @Test
- public void testAccessBearing() {
- Location location = new Location("");
- assertFalse(location.hasBearing());
-
- location.setBearing(1.0f);
- assertEquals(1.0, location.getBearing(), DELTA);
- assertTrue(location.hasBearing());
-
- location.setBearing(371.0f);
- assertEquals(11.0, location.getBearing(), DELTA);
- assertTrue(location.hasBearing());
-
- location.setBearing(-361.0f);
- assertEquals(359.0, location.getBearing(), DELTA);
- assertTrue(location.hasBearing());
- }
-
- @Test
- public void testAccessExtras() {
- Location location = createTestLocation();
-
- assertTestBundle(location.getExtras());
-
- location.setExtras(null);
- assertNull(location.getExtras());
- }
-
- @Test
- public void testAccessLatitude() {
- Location location = new Location("");
-
- location.setLatitude(0);
- assertEquals(0, location.getLatitude(), DELTA);
-
- location.setLatitude(90);
- assertEquals(90, location.getLatitude(), DELTA);
-
- location.setLatitude(-90);
- assertEquals(-90, location.getLatitude(), DELTA);
- }
-
- @Test
- public void testAccessLongitude() {
- Location location = new Location("");
-
- location.setLongitude(0);
- assertEquals(0, location.getLongitude(), DELTA);
-
- location.setLongitude(180);
- assertEquals(180, location.getLongitude(), DELTA);
-
- location.setLongitude(-180);
- assertEquals(-180, location.getLongitude(), DELTA);
- }
-
- @Test
- public void testAccessProvider() {
- Location location = new Location("");
-
- String provider = "Location Provider";
- location.setProvider(provider);
- assertEquals(provider, location.getProvider());
-
- location.setProvider(null);
- assertNull(location.getProvider());
- }
-
- @Test
- public void testAccessSpeed() {
- Location location = new Location("");
- assertFalse(location.hasSpeed());
-
- location.setSpeed(234.0045f);
- assertEquals(234.0045, location.getSpeed(), DELTA);
- assertTrue(location.hasSpeed());
- }
-
- @Test
- public void testAccessTime() {
- Location location = new Location("");
-
- location.setTime(0);
- assertEquals(0, location.getTime());
-
- location.setTime(Long.MAX_VALUE);
- assertEquals(Long.MAX_VALUE, location.getTime());
-
- location.setTime(12000);
- assertEquals(12000, location.getTime());
- }
-
- @Test
- public void testAccessElapsedRealtime() {
- Location location = new Location("");
-
- location.setElapsedRealtimeNanos(0);
- assertEquals(0, location.getElapsedRealtimeNanos());
-
- location.setElapsedRealtimeNanos(Long.MAX_VALUE);
- assertEquals(Long.MAX_VALUE, location.getElapsedRealtimeNanos());
-
- location.setElapsedRealtimeNanos(12000);
- assertEquals(12000, location.getElapsedRealtimeNanos());
- }
-
- @Test
- public void testAccessElapsedRealtimeUncertaintyNanos() {
- Location location = new Location("");
- assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
- assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
-
- location.setElapsedRealtimeUncertaintyNanos(12000.0);
- assertEquals(12000.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
- assertTrue(location.hasElapsedRealtimeUncertaintyNanos());
-
- location.reset();
- assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
- assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
- }
-
- @Test
- public void testSetMock() {
- Location location = new Location("");
- assertFalse(location.isMock());
- location.setMock(true);
- assertTrue(location.isMock());
- }
-
- @Test
- public void testSet() {
- Location location = new Location("");
-
- Location loc = createTestLocation();
-
- location.set(loc);
- assertTestLocation(location);
-
- location.reset();
- assertNull(location.getProvider());
- assertEquals(0, location.getTime());
- assertEquals(0, location.getLatitude(), DELTA);
- assertEquals(0, location.getLongitude(), DELTA);
- assertEquals(0, location.getAltitude(), DELTA);
- assertFalse(location.hasAltitude());
- assertEquals(0, location.getSpeed(), DELTA);
- assertFalse(location.hasSpeed());
- assertEquals(0, location.getBearing(), DELTA);
- assertFalse(location.hasBearing());
- assertEquals(0, location.getAccuracy(), DELTA);
- assertFalse(location.hasAccuracy());
-
- assertEquals(0, location.getVerticalAccuracyMeters(), DELTA);
- assertEquals(0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
- assertEquals(0, location.getBearingAccuracyDegrees(), DELTA);
-
- assertFalse(location.hasVerticalAccuracy());
- assertFalse(location.hasSpeedAccuracy());
- assertFalse(location.hasBearingAccuracy());
-
- assertNull(location.getExtras());
- }
-
- @Test
- public void testToString() {
- Location location = createTestLocation();
-
- assertNotNull(location.toString());
- }
-
- @Test
- public void testWriteToParcel() {
- Location location = createTestLocation();
+ public void testParcelRoundtrip() {
+ Location l = new Location("test");
+ l.setTime(1);
+ l.setElapsedRealtimeNanos(2);
+ l.setElapsedRealtimeUncertaintyNanos(3);
+ l.setLatitude(-90);
+ l.setLongitude(90);
+ l.setAltitude(100);
+ l.setVerticalAccuracyMeters(90);
+ l.setSpeed(1000);
+ l.setSpeedAccuracyMetersPerSecond(9);
+ l.setBearing(7);
+ l.setBearingAccuracyDegrees(11);
+ l.setMock(true);
+ Bundle b = new Bundle();
+ b.putString("key", "value");
+ l.setExtras(b);
Parcel parcel = Parcel.obtain();
- location.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- Location newLocation = Location.CREATOR.createFromParcel(parcel);
- assertTestLocation(newLocation);
-
- parcel.recycle();
- }
-
- private void assertTestLocation(Location l) {
- assertNotNull(l);
- assertEquals(TEST_PROVIDER, l.getProvider());
- assertEquals(TEST_ACCURACY, l.getAccuracy(), DELTA);
- assertEquals(TEST_VERTICAL_ACCURACY, l.getVerticalAccuracyMeters(), DELTA);
- assertEquals(TEST_SPEED_ACCURACY, l.getSpeedAccuracyMetersPerSecond(), DELTA);
- assertEquals(TEST_BEARING_ACCURACY, l.getBearingAccuracyDegrees(), DELTA);
- assertEquals(TEST_ALTITUDE, l.getAltitude(), DELTA);
- assertEquals(TEST_LATITUDE, l.getLatitude(), DELTA);
- assertEquals(TEST_BEARING, l.getBearing(), DELTA);
- assertEquals(TEST_LONGITUDE, l.getLongitude(), DELTA);
- assertEquals(TEST_SPEED, l.getSpeed(), DELTA);
- assertEquals(TEST_TIME, l.getTime());
- assertTestBundle(l.getExtras());
- }
-
- private Location createTestLocation() {
- Location l = new Location(TEST_PROVIDER);
- l.setAccuracy(TEST_ACCURACY);
- l.setVerticalAccuracyMeters(TEST_VERTICAL_ACCURACY);
- l.setSpeedAccuracyMetersPerSecond(TEST_SPEED_ACCURACY);
- l.setBearingAccuracyDegrees(TEST_BEARING_ACCURACY);
-
- l.setAltitude(TEST_ALTITUDE);
- l.setLatitude(TEST_LATITUDE);
- l.setBearing(TEST_BEARING);
- l.setLongitude(TEST_LONGITUDE);
- l.setSpeed(TEST_SPEED);
- l.setTime(TEST_TIME);
- Bundle bundle = new Bundle();
- bundle.putBoolean(TEST_KEY1NAME, TEST_KEY1VALUE);
- bundle.putByte(TEST_KEY2NAME, TEST_KEY2VALUE);
- l.setExtras(bundle);
-
- return l;
- }
-
- private void assertTestBundle(Bundle bundle) {
- assertFalse(bundle.getBoolean(TEST_KEY1NAME));
- assertEquals(TEST_KEY2VALUE, bundle.getByte(TEST_KEY2NAME));
+ try {
+ l.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ assertThat(Location.CREATOR.createFromParcel(parcel)).isEqualTo(l);
+ } finally {
+ parcel.recycle();
+ }
}
}
diff --git a/tests/media/src/android/mediav2/cts/MuxerTest.java b/tests/media/src/android/mediav2/cts/MuxerTest.java
index 10bfb68..33c6fdb 100644
--- a/tests/media/src/android/mediav2/cts/MuxerTest.java
+++ b/tests/media/src/android/mediav2/cts/MuxerTest.java
@@ -481,7 +481,7 @@
private native boolean nativeTestGetTrackFormat(String srcPath, String outPath,
int outFormat);
- private void verifyLocationInFile(String fileName) {
+ private void verifyLocationInFile(String fileName) throws IOException {
if (mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 &&
mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) return;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
@@ -514,7 +514,7 @@
retriever.release();
}
- private void verifyOrientation(String fileName) {
+ private void verifyOrientation(String fileName) throws IOException {
if (mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 &&
mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) return;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
@@ -695,14 +695,14 @@
}
@Test
- public void testSetLocationNative() {
+ public void testSetLocationNative() throws IOException {
Assume.assumeTrue(shouldRunTest(mOutFormat));
assertTrue(nativeTestSetLocation(mOutFormat, mInpPath, mOutPath));
verifyLocationInFile(mOutPath);
}
@Test
- public void testSetOrientationHintNative() {
+ public void testSetOrientationHintNative() throws IOException {
Assume.assumeTrue(shouldRunTest(mOutFormat));
assertTrue(nativeTestSetOrientationHint(mOutFormat, mInpPath, mOutPath));
verifyOrientation(mOutPath);
diff --git a/tests/mediapc/OWNERS b/tests/mediapc/OWNERS
index 091c4df..d61c4a2 100644
--- a/tests/mediapc/OWNERS
+++ b/tests/mediapc/OWNERS
@@ -1,7 +1,6 @@
# Bug component: 1344
# include media developers and framework video team
include platform/frameworks/av:/media/OWNERS
-chz@google.com
dichenzhang@google.com
essick@google.com
gokrishnan@google.com
diff --git a/tests/process/Android.bp b/tests/process/Android.bp
new file mode 100644
index 0000000..a70625f
--- /dev/null
+++ b/tests/process/Android.bp
@@ -0,0 +1,101 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// We build the main test APK and helper APKs from the same source code,
+// just so they can easily share the constants, etc.
+
+java_library {
+ name: "CtsProcessTestCore",
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "guava",
+ "truth-prebuilt",
+ "testng",
+ ],
+ libs: ["android.test.base"],
+ srcs: [
+ "src/**/*.java",
+ ],
+}
+
+java_defaults {
+ name: "CtsProcessTest_default",
+ static_libs: [
+ "CtsProcessTestCore",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
+
+android_test {
+ name: "CtsProcessTest",
+ defaults: [
+ "cts_defaults",
+ "CtsProcessTest_default",
+ ],
+ manifest: "AndroidManifest_main.xml",
+}
+
+android_test_helper_app {
+ name: "CtsProcessTestHelper1",
+ defaults: ["CtsProcessTest_default"],
+
+ manifest: "AndroidManifest_helper1.xml",
+ additional_manifests: [
+ "AndroidManifest_helper.xml",
+ ],
+ package_name: "android.os.cts.process.helper1",
+}
+
+android_test_helper_app {
+ name: "CtsProcessTestHelper2",
+ defaults: ["CtsProcessTest_default"],
+
+ manifest: "AndroidManifest_helper2.xml",
+ additional_manifests: [
+ "AndroidManifest_helper.xml",
+ ],
+ package_name: "android.os.cts.process.helper2",
+}
+
+android_test_helper_app {
+ name: "CtsProcessTestHelper3",
+ defaults: ["CtsProcessTest_default"],
+
+ manifest: "AndroidManifest_helper3.xml",
+ additional_manifests: [
+ "AndroidManifest_helper.xml",
+ ],
+ package_name: "android.os.cts.process.helper3",
+}
+
+android_test_helper_app {
+ name: "CtsProcessTestHelper4",
+ defaults: ["CtsProcessTest_default"],
+
+ manifest: "AndroidManifest_helper4.xml",
+ additional_manifests: [
+ "AndroidManifest_helper.xml",
+ ],
+ package_name: "android.os.cts.process.helper4",
+}
diff --git a/tests/process/AndroidManifest_helper.xml b/tests/process/AndroidManifest_helper.xml
new file mode 100644
index 0000000..45ac951
--- /dev/null
+++ b/tests/process/AndroidManifest_helper.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.process.helper" >
+
+ <application>
+ <receiver android:name=".MyReceiver0"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="ACTION_SEND_BACK_START_TIME" />
+ </intent-filter>
+ </receiver>
+ <receiver android:name=".MyReceiver1"
+ android:exported="true"
+ android:process=":sub1">
+ <intent-filter>
+ <action android:name="ACTION_SEND_BACK_START_TIME" />
+ </intent-filter>
+ </receiver>
+ <receiver android:name=".MyReceiver2"
+ android:exported="true"
+ android:process=":sub2">
+ <intent-filter>
+ <action android:name="ACTION_SEND_BACK_START_TIME" />
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/process/AndroidManifest_helper1.xml b/tests/process/AndroidManifest_helper1.xml
new file mode 100644
index 0000000..b59ea1b
--- /dev/null
+++ b/tests/process/AndroidManifest_helper1.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.process.helper" >
+
+ <application>
+ <processes>
+ <process /> <!-- For the main process -->
+ <process android:process=":sub1" android:name=".Application1" />
+ <process android:process=":sub2"/>
+ </processes>
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/process/AndroidManifest_helper2.xml b/tests/process/AndroidManifest_helper2.xml
new file mode 100644
index 0000000..89346bd
--- /dev/null
+++ b/tests/process/AndroidManifest_helper2.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.process.helper" >
+
+ <application android:name=".Application1" >
+ <processes>
+ <process /> <!-- For the main process -->
+ <process android:process=":sub1" android:name=".Application1b" />
+ <process android:process=":sub2" android:name=".Application2b" />
+ </processes>
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/process/AndroidManifest_helper3.xml b/tests/process/AndroidManifest_helper3.xml
new file mode 100644
index 0000000..9b9ef69
--- /dev/null
+++ b/tests/process/AndroidManifest_helper3.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.process.helper"
+ android:sharedUserId="android.os.cts.process.helper.shared">
+
+ <application android:process="android.os.cts.process.helper.shared_process">
+ <processes>
+ <process android:process="android.os.cts.process.helper.shared_process" />
+ <process android:process=":sub1" android:name=".Application1c" />
+ <process android:process=":sub2"/>
+ <process android:process="android.os.cts.process.helper.shared.sub3" android:name=".Application3" />
+ </processes>
+ <receiver android:name=".MyReceiver3"
+ android:exported="true"
+ android:process="android.os.cts.process.helper.shared.sub3">
+ <intent-filter>
+ <action android:name="ACTION_SEND_BACK_START_TIME" />
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/process/AndroidManifest_helper4.xml b/tests/process/AndroidManifest_helper4.xml
new file mode 100644
index 0000000..508dae2
--- /dev/null
+++ b/tests/process/AndroidManifest_helper4.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.process.helper"
+ android:sharedUserId="android.os.cts.process.helper.shared">
+
+ <application android:process="android.os.cts.process.helper.shared_process"
+ android:name=".Application1" >
+ <processes>
+ <process android:process="android.os.cts.process.helper.shared_process" />
+ <process android:process=":sub1"/>
+ <process android:process=":sub2" android:name=".Application2" />
+ <process android:process="android.os.cts.process.helper.shared.sub3" android:name=".Application3b" />
+ </processes>
+ <receiver android:name=".MyReceiver3"
+ android:exported="true"
+ android:process="android.os.cts.process.helper.shared.sub3">
+ <intent-filter>
+ <action android:name="ACTION_SEND_BACK_START_TIME" />
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/process/AndroidManifest_main.xml b/tests/process/AndroidManifest_main.xml
new file mode 100644
index 0000000..3ce892e
--- /dev/null
+++ b/tests/process/AndroidManifest_main.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.process" >
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.os.cts.process" />
+</manifest>
\ No newline at end of file
diff --git a/tests/process/AndroidTest.xml b/tests/process/AndroidTest.xml
new file mode 100644
index 0000000..e3e9897
--- /dev/null
+++ b/tests/process/AndroidTest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 BlobStore test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsProcessTest.apk" />
+ <option name="test-file-name" value="CtsProcessTestHelper1.apk" />
+ <option name="test-file-name" value="CtsProcessTestHelper2.apk" />
+ <option name="test-file-name" value="CtsProcessTestHelper3.apk" />
+ <option name="test-file-name" value="CtsProcessTestHelper4.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="am wait-for-broadcast-idle" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.os.cts.process" />
+ </test>
+</configuration>
diff --git a/tests/process/OWNERS b/tests/process/OWNERS
new file mode 100644
index 0000000..3b15c95
--- /dev/null
+++ b/tests/process/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 316234
+include platform/frameworks/base:/services/core/java/com/android/server/am/OWNERS
diff --git a/tests/process/src/android/os/cts/process/ProcessTest2.java b/tests/process/src/android/os/cts/process/ProcessTest2.java
new file mode 100644
index 0000000..c45577e
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/ProcessTest2.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process;
+
+import static android.os.cts.process.common.Consts.HELPER1_RECEIVER0;
+import static android.os.cts.process.common.Consts.HELPER1_RECEIVER1;
+import static android.os.cts.process.common.Consts.HELPER1_RECEIVER2;
+import static android.os.cts.process.common.Consts.HELPER2_RECEIVER0;
+import static android.os.cts.process.common.Consts.HELPER2_RECEIVER1;
+import static android.os.cts.process.common.Consts.HELPER2_RECEIVER2;
+import static android.os.cts.process.common.Consts.HELPER3_RECEIVER0;
+import static android.os.cts.process.common.Consts.HELPER3_RECEIVER1;
+import static android.os.cts.process.common.Consts.HELPER3_RECEIVER2;
+import static android.os.cts.process.common.Consts.HELPER3_RECEIVER3;
+import static android.os.cts.process.common.Consts.HELPER4_RECEIVER0;
+import static android.os.cts.process.common.Consts.HELPER4_RECEIVER1;
+import static android.os.cts.process.common.Consts.HELPER4_RECEIVER2;
+import static android.os.cts.process.common.Consts.HELPER4_RECEIVER3;
+import static android.os.cts.process.common.Consts.HELPER_SHARED_PROCESS_NAME;
+import static android.os.cts.process.common.Consts.PACKAGE_HELPER1;
+import static android.os.cts.process.common.Consts.PACKAGE_HELPER2;
+import static android.os.cts.process.common.Consts.PACKAGE_HELPER3;
+import static android.os.cts.process.common.Consts.PACKAGE_HELPER4;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.cts.process.common.Consts;
+import android.os.cts.process.common.Message;
+import android.util.Log;
+import android.util.LogPrinter;
+import android.util.Printer;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+
+import com.android.compatibility.common.util.BroadcastMessenger.Receiver;
+import com.android.compatibility.common.util.ShellUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * CTS for {@link android.os.Process}.
+ *
+ * We have more test in cts/tests/tests/os too.
+ */
+@RunWith(AndroidJUnit4ClassRunner.class)
+public class ProcessTest2 {
+ protected static final Context sContext = InstrumentationRegistry.getTargetContext();
+
+ /** Tell all the helper app processes to stop */
+ private static void stopAllHelperApps() throws Exception {
+ // Make sure all the broadcasts are delivered.
+ ShellUtils.runShellCommand("am wait-for-broadcast-idle");
+ Thread.sleep(500); // Just give the system a bit time to breathe.
+ ShellUtils.runShellCommand("am force-stop " + PACKAGE_HELPER1);
+ ShellUtils.runShellCommand("am force-stop " + PACKAGE_HELPER2);
+ ShellUtils.runShellCommand("am force-stop " + PACKAGE_HELPER3);
+ ShellUtils.runShellCommand("am force-stop " + PACKAGE_HELPER4);
+ Thread.sleep(500); // Just give the system a bit time to breathe.
+ }
+
+ public void checkStartTime(ComponentName cn, String expectedProcessName) throws Exception {
+ // Start the target process by sending a broadcast, and get back the results
+ // from the target APIs.
+ try (Receiver<Message> receiver = new Receiver<>(sContext, Consts.TAG)) {
+
+ // Start the first process.
+ Intent intent = new Intent(Consts.ACTION_SEND_BACK_START_TIME)
+ .setComponent(cn)
+ .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+ final long beforeStartElapsedRealtime = SystemClock.elapsedRealtime();
+ final long beforeStartUptimeMillis = SystemClock.uptimeMillis();
+
+ sContext.sendBroadcast(intent);
+
+ final Message m = receiver.waitForNextMessage();
+
+ // Check the start times.
+ assertThat(m.startRequestedElapsedRealtime).isAtLeast(beforeStartElapsedRealtime);
+ assertThat(m.startElapsedRealtime).isAtLeast(m.startRequestedElapsedRealtime);
+
+ assertThat(m.startRequestedUptimeMillis).isAtLeast(beforeStartUptimeMillis);
+ assertThat(m.startUptimeMillis).isAtLeast(m.startRequestedUptimeMillis);
+
+ // Check the process name.
+ assertThat(m.processName).isEqualTo(expectedProcessName);
+
+ // There may be more message, if the process has a custom app class, but ignore that.
+ }
+ }
+
+ /**
+ * Test for:
+ * {@link Process#getStartElapsedRealtime()}
+ * {@link Process#getStartUptimeMillis()}
+ * {@link Process#getStartRequestedElapsedRealtime()}
+ * {@link Process#getStartRequestedUptimeMillis()}
+ */
+ @Test
+ public void testStartTime() throws Exception {
+ stopAllHelperApps();
+
+ // Main process.
+ checkStartTime(HELPER1_RECEIVER0, PACKAGE_HELPER1);
+
+ // Sub process.
+ checkStartTime(HELPER1_RECEIVER1, PACKAGE_HELPER1 + ":sub1");
+ }
+
+ /**
+ * Test for:
+ * {@link Process#getStartElapsedRealtime()}
+ * {@link Process#getStartUptimeMillis()}
+ * {@link Process#getStartRequestedElapsedRealtime()}
+ * {@link Process#getStartRequestedUptimeMillis()}
+ *
+ * but for a shared process.
+ */
+ @Test
+ public void testStartTime_sharedProcess() throws Exception {
+ stopAllHelperApps();
+
+ try (Receiver<Message> receiver = new Receiver<>(sContext, Consts.TAG)) {
+
+ // Bring up the first package on the same process.
+ final Intent intent = new Intent(Consts.ACTION_SEND_BACK_START_TIME)
+ .setComponent(Consts.HELPER3_RECEIVER0)
+ .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+ final long beforeStartElapsedRealtime = SystemClock.elapsedRealtime();
+ final long beforeStartUptimeMillis = SystemClock.uptimeMillis();
+
+ sContext.sendBroadcast(intent);
+
+ final Message m = receiver.waitForNextMessage();
+
+ // Check the start times.
+
+ assertThat(m.startRequestedElapsedRealtime).isAtLeast(beforeStartElapsedRealtime);
+ assertThat(m.startElapsedRealtime).isAtLeast(m.startRequestedElapsedRealtime);
+
+ assertThat(m.startRequestedUptimeMillis).isAtLeast(beforeStartUptimeMillis);
+ assertThat(m.startUptimeMillis).isAtLeast(m.startRequestedUptimeMillis);
+
+ // Check the process name.
+ assertThat(m.processName).isEqualTo(HELPER_SHARED_PROCESS_NAME);
+
+ // Bring up the first package on the same process.
+ // The start request time should still be the same as the above result.
+ final Intent intent2 = new Intent(Consts.ACTION_SEND_BACK_START_TIME)
+ .setComponent(Consts.HELPER4_RECEIVER0)
+ .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+ sContext.sendBroadcast(intent);
+
+ final Message m2 = receiver.waitForNextMessage();
+
+ assertThat(m2.startRequestedElapsedRealtime)
+ .isEqualTo(m.startRequestedElapsedRealtime);
+ assertThat(m2.startRequestedUptimeMillis)
+ .isEqualTo(m.startRequestedUptimeMillis);
+
+ assertThat(m2.processName).isEqualTo(HELPER_SHARED_PROCESS_NAME);
+
+ receiver.ensureNoMoreMessages();
+ }
+ }
+
+ private void checkApplicationClass(ComponentName receiverComponent,
+ @NonNull String expectedPackageName, @NonNull String expectedProcessName,
+ @Nullable String expectedApplicationClassName) throws Exception {
+
+ // Start the target process by sending a receiver, and get back the results
+ // from the target APIs.
+ try (Receiver<Message> receiver = new Receiver<>(sContext, Consts.TAG)) {
+
+ // Start the first process.
+ Intent intent = new Intent(Consts.ACTION_SEND_BACK_START_TIME)
+ .setComponent(receiverComponent)
+ .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+ sContext.sendBroadcast(intent);
+
+ // If the process has a custom application class, then the first message should be from
+ // the application class.
+ if (expectedApplicationClassName != null) {
+ final Message m = receiver.waitForNextMessage();
+ Log.i(Consts.TAG, "Message (which is supposed to be from the application class): "
+ + m);
+
+ assertThat(m.packageName).isEqualTo(expectedPackageName);
+ assertThat(m.processName).isEqualTo(expectedProcessName);
+ assertThat(m.applicationClassName).isEqualTo(expectedApplicationClassName);
+ }
+
+ // Then there should be a message from the receiver.
+ final Message m = receiver.waitForNextMessage();
+ Log.i(Consts.TAG, "Message (which is supposed to be from the receiver): " + m);
+
+ assertThat(m.packageName).isEqualTo(expectedPackageName);
+ assertThat(m.processName).isEqualTo(expectedProcessName);
+
+ if (expectedApplicationClassName != null) {
+ assertThat(m.applicationContextClassName).isEqualTo(expectedApplicationClassName);
+ } else {
+ // No custom app class, so the default app class should be used.
+ assertThat(m.applicationContextClassName)
+ .isEqualTo(android.app.Application.class.getCanonicalName());
+ }
+
+ receiver.ensureNoMoreMessages();
+ }
+ }
+
+ /**
+ * Make sure the correct app class is instantiated in the app processes.
+ */
+ @Test
+ public void testApplicationClass() throws Exception {
+ stopAllHelperApps();
+
+ // Each receiver in each helper package runs on different processes, which may or may
+ // not have a custom application class.
+ checkApplicationClass(HELPER1_RECEIVER0, PACKAGE_HELPER1, PACKAGE_HELPER1,
+ null);
+ checkApplicationClass(HELPER1_RECEIVER1, PACKAGE_HELPER1, PACKAGE_HELPER1 + ":sub1",
+ "android.os.cts.process.helper.Application1");
+ checkApplicationClass(HELPER1_RECEIVER2, PACKAGE_HELPER1, PACKAGE_HELPER1 + ":sub2",
+ null);
+
+ checkApplicationClass(HELPER2_RECEIVER0, PACKAGE_HELPER2, PACKAGE_HELPER2,
+ "android.os.cts.process.helper.Application1");
+ checkApplicationClass(HELPER2_RECEIVER1, PACKAGE_HELPER2, PACKAGE_HELPER2 + ":sub1",
+ "android.os.cts.process.helper.Application1b");
+ checkApplicationClass(HELPER2_RECEIVER2, PACKAGE_HELPER2, PACKAGE_HELPER2 + ":sub2",
+ "android.os.cts.process.helper.Application2b");
+
+ checkApplicationClass(HELPER3_RECEIVER0, PACKAGE_HELPER3,
+ "android.os.cts.process.helper.shared_process",
+ null);
+ checkApplicationClass(HELPER3_RECEIVER1, PACKAGE_HELPER3, PACKAGE_HELPER3 + ":sub1",
+ "android.os.cts.process.helper.Application1c");
+ checkApplicationClass(HELPER3_RECEIVER2, PACKAGE_HELPER3, PACKAGE_HELPER3 + ":sub2",
+ null);
+ checkApplicationClass(HELPER3_RECEIVER3, PACKAGE_HELPER3,
+ "android.os.cts.process.helper.shared.sub3",
+ "android.os.cts.process.helper.Application3");
+
+ checkApplicationClass(HELPER4_RECEIVER0, PACKAGE_HELPER4,
+ "android.os.cts.process.helper.shared_process",
+ "android.os.cts.process.helper.Application1");
+ checkApplicationClass(HELPER4_RECEIVER1, PACKAGE_HELPER4, PACKAGE_HELPER4 + ":sub1",
+ "android.os.cts.process.helper.Application1");
+ checkApplicationClass(HELPER4_RECEIVER2, PACKAGE_HELPER4, PACKAGE_HELPER4 + ":sub2",
+ "android.os.cts.process.helper.Application2");
+ checkApplicationClass(HELPER4_RECEIVER3, PACKAGE_HELPER4,
+ "android.os.cts.process.helper.shared.sub3",
+ "android.os.cts.process.helper.Application3b");
+ }
+
+ /**
+ * This doesn't do any assertions, but just dump the ApplicationInfo's for the helper APKs
+ * on logcat, so if some of the tests fail, we can look at the log and verify the
+ * ApplicationInfo is correct.
+ *
+ * (`dumpsys package` doesn't have a way to dump ApplicationInfo at the moment.)
+ */
+ @Test
+ public void dumpApplicationInfo() throws Exception {
+ LogPrinter pw = new LogPrinter(Log.VERBOSE, Consts.TAG);
+ dumpApplicationInfo(pw, PACKAGE_HELPER1);
+ dumpApplicationInfo(pw, Consts.PACKAGE_HELPER2);
+ dumpApplicationInfo(pw, Consts.PACKAGE_HELPER3);
+ dumpApplicationInfo(pw, Consts.PACKAGE_HELPER4);
+ }
+
+ private void dumpApplicationInfo(Printer pw, String packageName) throws Exception {
+ ApplicationInfo ai = sContext.getPackageManager().getApplicationInfo(packageName, 0);
+ pw.println("Dumping " + packageName);
+ ai.dump(pw, " ");
+ }
+}
diff --git a/tests/process/src/android/os/cts/process/common/Consts.java b/tests/process/src/android/os/cts/process/common/Consts.java
new file mode 100644
index 0000000..dc7409f
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/common/Consts.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process.common;
+
+import android.content.ComponentName;
+import android.os.cts.process.helper.MyReceiver0;
+import android.os.cts.process.helper.MyReceiver1;
+import android.os.cts.process.helper.MyReceiver2;
+import android.os.cts.process.helper.MyReceiver3;
+
+public class Consts {
+ private Consts() {
+ }
+
+ public static final String TAG = "CtsProcessTest";
+
+ public static final String PACKAGE_HELPER1 = "android.os.cts.process.helper1";
+ public static final String PACKAGE_HELPER2 = "android.os.cts.process.helper2";
+ public static final String PACKAGE_HELPER3 = "android.os.cts.process.helper3";
+ public static final String PACKAGE_HELPER4 = "android.os.cts.process.helper4";
+
+ public static final String HELPER_SHARED_PROCESS_NAME =
+ "android.os.cts.process.helper.shared_process";
+
+ private static ComponentName buildReceiver(String packageName, int receiverId) {
+ switch (receiverId) {
+ case 0:
+ return new ComponentName(packageName, MyReceiver0.class.getName());
+ case 1:
+ return new ComponentName(packageName, MyReceiver1.class.getName());
+ case 2:
+ return new ComponentName(packageName, MyReceiver2.class.getName());
+ case 3:
+ return new ComponentName(packageName, MyReceiver3.class.getName());
+ default:
+ throw new RuntimeException("Unsupported ID detected: " + receiverId);
+ }
+ }
+
+ public static final ComponentName HELPER1_RECEIVER0 = buildReceiver(PACKAGE_HELPER1, 0);
+ public static final ComponentName HELPER1_RECEIVER1 = buildReceiver(PACKAGE_HELPER1, 1);
+ public static final ComponentName HELPER1_RECEIVER2 = buildReceiver(PACKAGE_HELPER1, 2);
+
+ public static final ComponentName HELPER2_RECEIVER0 = buildReceiver(PACKAGE_HELPER2, 0);
+ public static final ComponentName HELPER2_RECEIVER1 = buildReceiver(PACKAGE_HELPER2, 1);
+ public static final ComponentName HELPER2_RECEIVER2 = buildReceiver(PACKAGE_HELPER2, 2);
+
+ public static final ComponentName HELPER3_RECEIVER0 = buildReceiver(PACKAGE_HELPER3, 0);
+ public static final ComponentName HELPER3_RECEIVER1 = buildReceiver(PACKAGE_HELPER3, 1);
+ public static final ComponentName HELPER3_RECEIVER2 = buildReceiver(PACKAGE_HELPER3, 2);
+ public static final ComponentName HELPER3_RECEIVER3 = buildReceiver(PACKAGE_HELPER3, 3);
+
+ public static final ComponentName HELPER4_RECEIVER0 = buildReceiver(PACKAGE_HELPER4, 0);
+ public static final ComponentName HELPER4_RECEIVER1 = buildReceiver(PACKAGE_HELPER4, 1);
+ public static final ComponentName HELPER4_RECEIVER2 = buildReceiver(PACKAGE_HELPER4, 2);
+ public static final ComponentName HELPER4_RECEIVER3 = buildReceiver(PACKAGE_HELPER4, 3);
+
+ public static final String ACTION_SEND_BACK_START_TIME = "ACTION_SEND_BACK_START_TIME";
+}
diff --git a/tests/process/src/android/os/cts/process/common/Message.java b/tests/process/src/android/os/cts/process/common/Message.java
new file mode 100644
index 0000000..f7b7773
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/common/Message.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process.common;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.SystemClock;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * codegen ${ANDROID_BUILD_TOP}/cts/tests/process/src/android/os/cts/process/common/Message.java
+ */
+@DataClass(
+ genConstructor = false,
+ genSetters = false,
+ genToString = true,
+ genAidl = false)
+public class Message implements Parcelable {
+ public Message() {
+ }
+
+ public void fillInBasicInfo(Context context) {
+ // codegen fails for whatever reason if it's after the fields.
+ packageName = context.getPackageName();
+ processName = Process.myProcessName();
+
+ applicationContextClassName = context.getApplicationContext().getClass().getCanonicalName();
+
+ nowElapsedRealtime = SystemClock.elapsedRealtime();
+ nowUptimeMillis = SystemClock.uptimeMillis();
+
+ startElapsedRealtime = Process.getStartElapsedRealtime();
+ startUptimeMillis = Process.getStartUptimeMillis();
+ startRequestedElapsedRealtime = Process.getStartRequestedElapsedRealtime();
+ startRequestedUptimeMillis = Process.getStartRequestedUptimeMillis();
+ }
+
+ @Nullable
+ public String packageName;
+ @Nullable
+ public String applicationClassName;
+ @Nullable
+ public String receiverClassName;
+ @Nullable
+ public String applicationContextClassName;
+ @Nullable
+ public String processName;
+ public long startElapsedRealtime;
+ public long startUptimeMillis;
+ public long startRequestedElapsedRealtime;
+ public long startRequestedUptimeMillis;
+ public long nowElapsedRealtime;
+ public long nowUptimeMillis;
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/cts/tests/process/src/android/os/cts/process/common/Message.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "Message { " +
+ "packageName = " + packageName + ", " +
+ "applicationClassName = " + applicationClassName + ", " +
+ "receiverClassName = " + receiverClassName + ", " +
+ "applicationContextClassName = " + applicationContextClassName + ", " +
+ "processName = " + processName + ", " +
+ "startElapsedRealtime = " + startElapsedRealtime + ", " +
+ "startUptimeMillis = " + startUptimeMillis + ", " +
+ "startRequestedElapsedRealtime = " + startRequestedElapsedRealtime + ", " +
+ "startRequestedUptimeMillis = " + startRequestedUptimeMillis + ", " +
+ "nowElapsedRealtime = " + nowElapsedRealtime + ", " +
+ "nowUptimeMillis = " + nowUptimeMillis +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ int flg = 0;
+ if (packageName != null) flg |= 0x1;
+ if (applicationClassName != null) flg |= 0x2;
+ if (receiverClassName != null) flg |= 0x4;
+ if (applicationContextClassName != null) flg |= 0x8;
+ if (processName != null) flg |= 0x10;
+ dest.writeInt(flg);
+ if (packageName != null) dest.writeString(packageName);
+ if (applicationClassName != null) dest.writeString(applicationClassName);
+ if (receiverClassName != null) dest.writeString(receiverClassName);
+ if (applicationContextClassName != null) dest.writeString(applicationContextClassName);
+ if (processName != null) dest.writeString(processName);
+ dest.writeLong(startElapsedRealtime);
+ dest.writeLong(startUptimeMillis);
+ dest.writeLong(startRequestedElapsedRealtime);
+ dest.writeLong(startRequestedUptimeMillis);
+ dest.writeLong(nowElapsedRealtime);
+ dest.writeLong(nowUptimeMillis);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected Message(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int flg = in.readInt();
+ String _packageName = (flg & 0x1) == 0 ? null : in.readString();
+ String _applicationClassName = (flg & 0x2) == 0 ? null : in.readString();
+ String _receiverClassName = (flg & 0x4) == 0 ? null : in.readString();
+ String _applicationContextClassName = (flg & 0x8) == 0 ? null : in.readString();
+ String _processName = (flg & 0x10) == 0 ? null : in.readString();
+ long _startElapsedRealtime = in.readLong();
+ long _startUptimeMillis = in.readLong();
+ long _startRequestedElapsedRealtime = in.readLong();
+ long _startRequestedUptimeMillis = in.readLong();
+ long _nowElapsedRealtime = in.readLong();
+ long _nowUptimeMillis = in.readLong();
+
+ this.packageName = _packageName;
+ this.applicationClassName = _applicationClassName;
+ this.receiverClassName = _receiverClassName;
+ this.applicationContextClassName = _applicationContextClassName;
+ this.processName = _processName;
+ this.startElapsedRealtime = _startElapsedRealtime;
+ this.startUptimeMillis = _startUptimeMillis;
+ this.startRequestedElapsedRealtime = _startRequestedElapsedRealtime;
+ this.startRequestedUptimeMillis = _startRequestedUptimeMillis;
+ this.nowElapsedRealtime = _nowElapsedRealtime;
+ this.nowUptimeMillis = _nowUptimeMillis;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<Message> CREATOR
+ = new Parcelable.Creator<Message>() {
+ @Override
+ public Message[] newArray(int size) {
+ return new Message[size];
+ }
+
+ @Override
+ public Message createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+ return new Message(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1639154672715L,
+ codegenVersion = "1.0.23",
+ sourceFile = "cts/tests/process/src/android/os/cts/process/common/Message.java",
+ inputSignatures = "public @android.annotation.Nullable java.lang.String packageName\npublic @android.annotation.Nullable java.lang.String applicationClassName\npublic @android.annotation.Nullable java.lang.String receiverClassName\npublic @android.annotation.Nullable java.lang.String applicationContextClassName\npublic @android.annotation.Nullable java.lang.String processName\npublic long startElapsedRealtime\npublic long startUptimeMillis\npublic long startRequestedElapsedRealtime\npublic long startRequestedUptimeMillis\npublic long nowElapsedRealtime\npublic long nowUptimeMillis\npublic void fillInBasicInfo(android.content.Context)\nclass Message extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=false, genToString=true, genAidl=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/tests/process/src/android/os/cts/process/helper/Application0.java b/tests/process/src/android/os/cts/process/helper/Application0.java
new file mode 100644
index 0000000..8c9a860
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/Application0.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process.helper;
+
+public class Application0 extends BaseApplication {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/Application1.java b/tests/process/src/android/os/cts/process/helper/Application1.java
new file mode 100644
index 0000000..4deae2c
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/Application1.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process.helper;
+
+public class Application1 extends BaseApplication {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/Application1b.java b/tests/process/src/android/os/cts/process/helper/Application1b.java
new file mode 100644
index 0000000..28efd23
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/Application1b.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process.helper;
+
+public class Application1b extends BaseApplication {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/Application1c.java b/tests/process/src/android/os/cts/process/helper/Application1c.java
new file mode 100644
index 0000000..b083e26
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/Application1c.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process.helper;
+
+public class Application1c extends BaseApplication {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/Application2.java b/tests/process/src/android/os/cts/process/helper/Application2.java
new file mode 100644
index 0000000..396ef79
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/Application2.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process.helper;
+
+public class Application2 extends BaseApplication {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/Application2b.java b/tests/process/src/android/os/cts/process/helper/Application2b.java
new file mode 100644
index 0000000..c88300f
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/Application2b.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process.helper;
+
+public class Application2b extends BaseApplication {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/Application3.java b/tests/process/src/android/os/cts/process/helper/Application3.java
new file mode 100644
index 0000000..b238f51
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/Application3.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process.helper;
+
+public class Application3 extends BaseApplication {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/Application3b.java b/tests/process/src/android/os/cts/process/helper/Application3b.java
new file mode 100644
index 0000000..ab7eef9
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/Application3b.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process.helper;
+
+public class Application3b extends BaseApplication {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/BaseApplication.java b/tests/process/src/android/os/cts/process/helper/BaseApplication.java
new file mode 100644
index 0000000..a9aa1f9
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/BaseApplication.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process.helper;
+
+import android.app.Application;
+import android.os.cts.process.common.Consts;
+import android.os.cts.process.common.Message;
+import android.util.Log;
+
+import com.android.compatibility.common.util.BroadcastMessenger;
+
+public abstract class BaseApplication extends Application {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ Log.i(Consts.TAG, "onCreate: this=" + this);
+
+ sendBackApplicationCreated();
+ }
+
+ private void sendBackApplicationCreated() {
+ Message m = new Message();
+
+ m.fillInBasicInfo(this);
+
+ m.applicationClassName = this.getClass().getCanonicalName();
+
+ BroadcastMessenger.send(this, Consts.TAG, m);
+ }
+}
diff --git a/tests/process/src/android/os/cts/process/helper/BaseReceiver.java b/tests/process/src/android/os/cts/process/helper/BaseReceiver.java
new file mode 100644
index 0000000..0e4f8e1
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/BaseReceiver.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process.helper;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.cts.process.common.Consts;
+import android.os.cts.process.common.Message;
+import android.util.Log;
+
+import com.android.compatibility.common.util.BroadcastMessenger;
+
+public class BaseReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(Consts.TAG, "onReceive: " + intent);
+ switch (intent.getAction()) {
+ case Consts.ACTION_SEND_BACK_START_TIME:
+ sendBackStartTime(context);
+ break;
+ }
+ }
+
+ private void sendBackStartTime(Context context) {
+ Message m = new Message();
+
+ m.fillInBasicInfo(context);
+
+ m.receiverClassName = this.getClass().getCanonicalName();
+
+ BroadcastMessenger.send(context, Consts.TAG, m);
+ }
+}
diff --git a/tests/process/src/android/os/cts/process/helper/MyReceiver0.java b/tests/process/src/android/os/cts/process/helper/MyReceiver0.java
new file mode 100644
index 0000000..a5237a2
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/MyReceiver0.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process.helper;
+
+public class MyReceiver0 extends BaseReceiver {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/MyReceiver1.java b/tests/process/src/android/os/cts/process/helper/MyReceiver1.java
new file mode 100644
index 0000000..a349fab
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/MyReceiver1.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process.helper;
+
+public class MyReceiver1 extends BaseReceiver {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/MyReceiver2.java b/tests/process/src/android/os/cts/process/helper/MyReceiver2.java
new file mode 100644
index 0000000..d3b41a3
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/MyReceiver2.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process.helper;
+
+public class MyReceiver2 extends BaseReceiver {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/MyReceiver3.java b/tests/process/src/android/os/cts/process/helper/MyReceiver3.java
new file mode 100644
index 0000000..075b796
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/MyReceiver3.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.process.helper;
+
+public class MyReceiver3 extends BaseReceiver {
+}
diff --git a/tests/quicksettings/Android.bp b/tests/quicksettings/Android.bp
new file mode 100644
index 0000000..7246788
--- /dev/null
+++ b/tests/quicksettings/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsTileServiceTestCases",
+ defaults: ["cts_defaults"],
+
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "androidx.test.uiautomator_uiautomator",
+ "compatibility-device-util-axt",
+ ],
+
+ libs: [
+ "android.test.mock",
+ "android.test.runner",
+ "android.test.base",
+ ],
+
+ srcs: ["src/**/*.java"],
+ sdk_version: "test_current",
+}
diff --git a/tests/quicksettings/AndroidManifest.xml b/tests/quicksettings/AndroidManifest.xml
new file mode 100644
index 0000000..225042d
--- /dev/null
+++ b/tests/quicksettings/AndroidManifest.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.quicksettings.cts">
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ <service android:name=".TestTileService"
+ android:exported="true"
+ android:label="TestTileService"
+ android:icon="@drawable/robot"
+ android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+ <intent-filter>
+ <action android:name="android.service.quicksettings.action.QS_TILE"/>
+ </intent-filter>
+ </service>
+
+ <service android:name=".ToggleableTestTileService"
+ android:exported="true"
+ android:label="BooleanTestTileService"
+ android:icon="@drawable/robot"
+ android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+ <intent-filter>
+ <action android:name="android.service.quicksettings.action.QS_TILE"/>
+ </intent-filter>
+ <meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE"
+ android:value="true"/>
+ </service>
+ </application>
+
+ <!-- self-instrumenting test package. -->
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.quicksettings.cts">
+ </instrumentation>
+</manifest>
diff --git a/tests/quicksettings/AndroidTest.xml b/tests/quicksettings/AndroidTest.xml
new file mode 100644
index 0000000..2a2acd9
--- /dev/null
+++ b/tests/quicksettings/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 CtsTileServiceTestCases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsTileServiceTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.quicksettings.cts" />
+ </test>
+</configuration>
diff --git a/tests/quicksettings/OWNERS b/tests/quicksettings/OWNERS
new file mode 100644
index 0000000..43c3658
--- /dev/null
+++ b/tests/quicksettings/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 78930
+kozynski@google.com
+juliacr@google.com
+evanlaird@google.com
\ No newline at end of file
diff --git a/tests/quicksettings/TEST_MAPPING b/tests/quicksettings/TEST_MAPPING
new file mode 100644
index 0000000..8feb0b3
--- /dev/null
+++ b/tests/quicksettings/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsTileServiceTestCases"
+ }
+ ]
+}
diff --git a/tests/quicksettings/res/drawable/robot.png b/tests/quicksettings/res/drawable/robot.png
new file mode 100644
index 0000000..8a9e698
--- /dev/null
+++ b/tests/quicksettings/res/drawable/robot.png
Binary files differ
diff --git a/tests/quicksettings/src/android/quicksettings/cts/BaseTileServiceTest.java b/tests/quicksettings/src/android/quicksettings/cts/BaseTileServiceTest.java
new file mode 100644
index 0000000..8ef5807
--- /dev/null
+++ b/tests/quicksettings/src/android/quicksettings/cts/BaseTileServiceTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.quicksettings.cts;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.service.quicksettings.TileService;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+public abstract class BaseTileServiceTest {
+
+ protected abstract String getTag();
+ protected abstract String getComponentName();
+ protected abstract TileService getTileServiceInstance();
+ protected abstract void waitForConnected(boolean state) throws InterruptedException;
+ protected abstract void waitForListening(boolean state) throws InterruptedException;
+ protected Context mContext;
+
+ final static String DUMP_COMMAND =
+ "dumpsys activity service com.android.systemui/.SystemUIService QSTileHost";
+
+ // Time between checks for state we expect.
+ protected static final long CHECK_DELAY = 250;
+ // Number of times to check before failing. This is set so the maximum wait time is about 4s,
+ // as some tests were observed to take around 3s.
+ protected static final long CHECK_RETRIES = 15;
+ // Timeout to wait for launcher
+ protected static final long TIMEOUT = 8000;
+
+ protected TileService mTileService;
+ private Intent homeIntent;
+ private String mLauncherPackage;
+
+ @Before
+ public void setUp() throws Exception {
+ assumeTrue(TileService.isQuickSettingsSupported());
+ mContext = InstrumentationRegistry.getContext();
+ homeIntent = new Intent(Intent.ACTION_MAIN);
+ homeIntent.addCategory(Intent.CATEGORY_HOME);
+
+ mLauncherPackage = mContext.getPackageManager().resolveActivity(homeIntent,
+ PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
+
+ // Wait for home
+ UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ device.pressHome();
+ device.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ expandSettings(false);
+ toggleServiceAccess(getComponentName(), false);
+ waitForConnected(false);
+ assertNull(TestTileService.getInstance());
+ }
+
+ protected void startTileService() throws Exception {
+ toggleServiceAccess(getComponentName(), true);
+ waitForConnected(true); // wait for service to be bound
+ mTileService = getTileServiceInstance();
+ assertNotNull(mTileService);
+ }
+
+ protected void toggleServiceAccess(String componentName, boolean on) throws Exception {
+ String command = " cmd statusbar " + (on ? "add-tile " : "remove-tile ")
+ + componentName;
+
+ executeShellCommand(command);
+ }
+
+ public String executeShellCommand(String command) throws IOException {
+ Log.i(getTag(), "Shell command: " + command);
+ try {
+ return SystemUtil.runShellCommand(getInstrumentation(), command);
+ } catch (IOException e) {
+ //bubble it up
+ Log.e(getTag(), "Error running shell command: " + command);
+ throw new IOException(e);
+ }
+ }
+
+ protected void expandSettings(boolean expand) throws Exception {
+ executeShellCommand(" cmd statusbar " + (expand ? "expand-settings" : "collapse"));
+ Thread.sleep(600); // wait for animation
+ }
+
+ protected void initializeAndListen() throws Exception {
+ startTileService();
+ expandSettings(true);
+ waitForListening(true);
+ }
+
+ /**
+ * Find a line containing {@code label} in {@code lines}.
+ */
+ protected String findLine(String[] lines, CharSequence label) {
+ for (String line: lines) {
+ if (line.contains(label)) {
+ return line;
+ }
+ }
+ return null;
+ }
+}
diff --git a/tests/quicksettings/src/android/quicksettings/cts/BooleanTileServiceTest.java b/tests/quicksettings/src/android/quicksettings/cts/BooleanTileServiceTest.java
new file mode 100644
index 0000000..0f6d56e
--- /dev/null
+++ b/tests/quicksettings/src/android/quicksettings/cts/BooleanTileServiceTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.quicksettings.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.TileService;
+
+import org.junit.Test;
+
+public class BooleanTileServiceTest extends BaseTileServiceTest {
+ private final static String TAG = "BooleanTileServiceTest";
+
+ @Test
+ public void testTileIsBoundAndListening() throws Exception {
+ startTileService();
+ expandSettings(true);
+ waitForListening(true);
+ }
+
+ @Test
+ public void testTileInDumpAndHasBooleanState() throws Exception {
+ initializeAndListen();
+
+ final CharSequence tileLabel = mTileService.getQsTile().getLabel();
+
+ final String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
+ final String line = findLine(dumpLines, tileLabel);
+ assertNotNull(line);
+ assertTrue(line.trim().startsWith("BooleanState"));
+ }
+
+ @Test
+ public void testTileStartsInactive() throws Exception {
+ initializeAndListen();
+
+ assertEquals(Tile.STATE_INACTIVE, mTileService.getQsTile().getState());
+ }
+
+ @Test
+ public void testValueTracksState() throws Exception {
+ initializeAndListen();
+
+ final CharSequence tileLabel = mTileService.getQsTile().getLabel();
+
+ String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
+ String line = findLine(dumpLines, tileLabel);
+
+ // Tile starts inactive
+ assertTrue(line.contains("value=false"));
+
+ ((ToggleableTestTileService) mTileService).toggleState();
+
+ // Close and open QS to make sure that state is refreshed
+ expandSettings(false);
+ waitForListening(false);
+ expandSettings(true);
+ waitForListening(true);
+
+ assertEquals(Tile.STATE_ACTIVE, mTileService.getQsTile().getState());
+
+ dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
+ line = findLine(dumpLines, tileLabel);
+
+ assertTrue(line.contains("value=true"));
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected String getComponentName() {
+ return ToggleableTestTileService.getComponentName().flattenToString();
+ }
+
+ @Override
+ protected TileService getTileServiceInstance() {
+ return ToggleableTestTileService.getInstance();
+ }
+
+ /**
+ * Waits for the TileService to be in the expected listening state. If it times out, it fails
+ * the test
+ * @param state desired listening state
+ * @throws InterruptedException
+ */
+ @Override
+ protected void waitForListening(boolean state) throws InterruptedException {
+ int ct = 0;
+ while (ToggleableTestTileService.isListening() != state && (ct++ < CHECK_RETRIES)) {
+ Thread.sleep(CHECK_DELAY);
+ }
+ assertEquals(state, ToggleableTestTileService.isListening());
+ }
+
+ /**
+ * Waits for the TileService to be in the expected connected state. If it times out, it fails
+ * the test
+ * @param state desired connected state
+ * @throws InterruptedException
+ */
+ @Override
+ protected void waitForConnected(boolean state) throws InterruptedException {
+ int ct = 0;
+ while (ToggleableTestTileService.isConnected() != state && (ct++ < CHECK_RETRIES)) {
+ Thread.sleep(CHECK_DELAY);
+ }
+ assertEquals(state, ToggleableTestTileService.isConnected());
+ }
+}
diff --git a/tests/quicksettings/src/android/quicksettings/cts/TestTileService.java b/tests/quicksettings/src/android/quicksettings/cts/TestTileService.java
new file mode 100644
index 0000000..0e6d099
--- /dev/null
+++ b/tests/quicksettings/src/android/quicksettings/cts/TestTileService.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.quicksettings.cts;
+
+import android.content.ComponentName;
+import android.service.quicksettings.TileService;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class TestTileService extends TileService {
+
+ public static final String TAG = "TestTileService";
+ public static final String PKG = "android.app.stubs";
+ public static final int ICON_ID = R.drawable.robot;
+
+ private static TestTileService sTestTileService = null;
+ AtomicBoolean isConnected = new AtomicBoolean(false);
+ AtomicBoolean isListening = new AtomicBoolean(false);
+ AtomicBoolean hasBeenClicked = new AtomicBoolean(false);
+
+ public static String getId() {
+ return String.format("%s/%s", TestTileService.class.getPackage().getName(),
+ TestTileService.class.getName());
+ }
+
+ public static ComponentName getComponentName() {
+ return new ComponentName(TestTileService.class.getPackage().getName(),
+ TestTileService.class.getName());
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ }
+
+ public static TestTileService getInstance() {
+ return TestTileService.sTestTileService;
+ }
+
+ public void setInstance(TestTileService tile) {
+ sTestTileService = tile;
+ }
+
+ public static boolean isConnected() {
+ return getInstance() != null && getInstance().isConnected.get();
+ }
+
+ public static boolean isListening() {
+ return getInstance().isListening.get();
+ }
+
+ public static boolean hasBeenClicked() {
+ return getInstance().hasBeenClicked.get();
+ }
+
+ @Override
+ public void onStartListening() {
+ super.onStartListening();
+ isListening.set(true);
+ }
+
+ @Override
+ public void onStopListening() {
+ super.onStopListening();
+ isListening.set(false);
+ }
+
+ @Override
+ public void onClick() {
+ super.onClick();
+ hasBeenClicked.set(true);
+ }
+
+ @Override
+ public void onTileAdded() {
+ super.onTileAdded();
+ setInstance(this);
+ isConnected.set(true);
+ }
+
+ @Override
+ public void onTileRemoved() {
+ super.onTileRemoved();
+ setInstance(null);
+ isConnected.set(false);
+ isListening.set(false);
+ hasBeenClicked.set(false);
+ }
+}
diff --git a/tests/quicksettings/src/android/quicksettings/cts/TileServiceTest.java b/tests/quicksettings/src/android/quicksettings/cts/TileServiceTest.java
new file mode 100644
index 0000000..2ccab0c
--- /dev/null
+++ b/tests/quicksettings/src/android/quicksettings/cts/TileServiceTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.quicksettings.cts;
+
+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.AlertDialog;
+import android.app.Dialog;
+import android.os.Looper;
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.TileService;
+
+import org.junit.Test;
+
+public class TileServiceTest extends BaseTileServiceTest {
+ private final static String TAG = "TileServiceTest";
+
+ @Test
+ public void testCreateTileService() {
+ final TileService tileService = new TileService();
+ }
+
+ @Test
+ public void testListening() throws Exception {
+ initializeAndListen();
+ }
+
+ @Test
+ public void testListening_stopped() throws Exception {
+ initializeAndListen();
+ expandSettings(false);
+ waitForListening(false);
+ }
+
+ @Test
+ public void testLocked_deviceNotLocked() throws Exception {
+ initializeAndListen();
+ assertFalse(mTileService.isLocked());
+ }
+
+ @Test
+ public void testSecure_deviceNotSecure() throws Exception {
+ initializeAndListen();
+ assertFalse(mTileService.isSecure());
+ }
+
+ @Test
+ public void testTile_hasCorrectIcon() throws Exception {
+ initializeAndListen();
+ Tile tile = mTileService.getQsTile();
+ assertEquals(TestTileService.ICON_ID, tile.getIcon().getResId());
+ }
+
+ @Test
+ public void testTile_hasCorrectSubtitle() throws Exception {
+ initializeAndListen();
+
+ Tile tile = mTileService.getQsTile();
+ tile.setSubtitle("test_subtitle");
+ tile.updateTile();
+ assertEquals("test_subtitle", tile.getSubtitle());
+ }
+
+ @Test
+ public void testTile_hasCorrectStateDescription() throws Exception {
+ initializeAndListen();
+
+ Tile tile = mTileService.getQsTile();
+ tile.setStateDescription("test_stateDescription");
+ tile.updateTile();
+ assertEquals("test_stateDescription", tile.getStateDescription());
+ }
+
+ @Test
+ public void testShowDialog() throws Exception {
+ Looper.prepare();
+ Dialog dialog = new AlertDialog.Builder(mContext).create();
+ initializeAndListen();
+ clickTile(TestTileService.getComponentName().flattenToString());
+ waitForClick();
+
+ mTileService.showDialog(dialog);
+
+ assertTrue(dialog.isShowing());
+ dialog.dismiss();
+ }
+
+ @Test
+ public void testUnlockAndRun_phoneIsUnlockedActivityIsRun() throws Exception {
+ initializeAndListen();
+ assertFalse(mTileService.isLocked());
+
+ TestRunnable testRunnable = new TestRunnable();
+
+ mTileService.unlockAndRun(testRunnable);
+ Thread.sleep(100); // wait for activity to run
+ waitForRun(testRunnable);
+ }
+
+ @Test
+ public void testTileInDumpAndHasState() throws Exception {
+ initializeAndListen();
+
+ final CharSequence tileLabel = mTileService.getQsTile().getLabel();
+
+ final String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
+ final String line = findLine(dumpLines, tileLabel);
+ assertNotNull(line);
+ assertTrue(line.trim().startsWith("State")); // Not BooleanState
+ }
+
+ private void clickTile(String componentName) throws Exception {
+ executeShellCommand(" cmd statusbar click-tile " + componentName);
+ }
+
+ /**
+ * Waits for the TileService to receive the clicked event. If it times out it fails the test.
+ * @throws InterruptedException
+ */
+ private void waitForClick() throws InterruptedException {
+ int ct = 0;
+ while (!TestTileService.hasBeenClicked() && (ct++ < CHECK_RETRIES)) {
+ Thread.sleep(CHECK_DELAY);
+ }
+ assertTrue(TestTileService.hasBeenClicked());
+ }
+
+ /**
+ * Waits for the runnable to be run. If it times out it fails the test.
+ * @throws InterruptedException
+ */
+ private void waitForRun(TestRunnable t) throws InterruptedException {
+ int ct = 0;
+ while (!t.hasRan && (ct++ < CHECK_RETRIES)) {
+ Thread.sleep(CHECK_DELAY);
+ }
+ assertTrue(t.hasRan);
+ }
+
+ @Override
+ protected void waitForListening(boolean state) throws InterruptedException {
+ int ct = 0;
+ while (TestTileService.isListening() != state && (ct++ < CHECK_RETRIES)) {
+ Thread.sleep(CHECK_DELAY);
+ }
+ assertEquals(state, TestTileService.isListening());
+ }
+
+ /**
+ * Waits for the TileService to be in the expected connected state. If it times out, it fails
+ * the test
+ * @param state desired connected state
+ * @throws InterruptedException
+ */
+ @Override
+ protected void waitForConnected(boolean state) throws InterruptedException {
+ int ct = 0;
+ while (TestTileService.isConnected() != state && (ct++ < CHECK_RETRIES)) {
+ Thread.sleep(CHECK_DELAY);
+ }
+ assertEquals(state, TestTileService.isConnected());
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected String getComponentName() {
+ return TestTileService.getComponentName().flattenToString();
+ }
+
+ @Override
+ protected TileService getTileServiceInstance() {
+ return TestTileService.getInstance();
+ }
+
+ class TestRunnable implements Runnable {
+ boolean hasRan = false;
+
+ @Override
+ public void run() {
+ hasRan = true;
+ }
+ }
+}
diff --git a/tests/quicksettings/src/android/quicksettings/cts/ToggleableTestTileService.java b/tests/quicksettings/src/android/quicksettings/cts/ToggleableTestTileService.java
new file mode 100644
index 0000000..0e2e660
--- /dev/null
+++ b/tests/quicksettings/src/android/quicksettings/cts/ToggleableTestTileService.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.quicksettings.cts;
+
+import android.content.ComponentName;
+import android.service.quicksettings.Tile;
+
+public class ToggleableTestTileService extends TestTileService {
+ public static final String TAG = "ToggleableTestTileService";
+ public static final String PKG = "android.app.stubs";
+ public static final int ICON_ID = R.drawable.robot;
+
+ private static TestTileService sTestTileService = null;
+
+ public static boolean isConnected() {
+ return getInstance() != null && getInstance().isConnected.get();
+ }
+
+ public static boolean isListening() {
+ return getInstance().isListening.get();
+ }
+
+ public static TestTileService getInstance() {
+ return ToggleableTestTileService.sTestTileService;
+ }
+
+ @Override
+ public void setInstance(TestTileService tile) {
+ sTestTileService = tile;
+ }
+
+ public static String getId() {
+ return String.format("%s/%s", ToggleableTestTileService.class.getPackage().getName(),
+ ToggleableTestTileService.class.getName());
+ }
+
+ public static ComponentName getComponentName() {
+ return new ComponentName(ToggleableTestTileService.class.getPackage().getName(),
+ ToggleableTestTileService.class.getName());
+ }
+
+ public void toggleState() {
+ if (isListening()) {
+ Tile tile = getInstance().getQsTile();
+ switch(tile.getState()) {
+ case Tile.STATE_ACTIVE:
+ tile.setState(Tile.STATE_INACTIVE);
+ break;
+ case Tile.STATE_INACTIVE:
+ tile.setState(Tile.STATE_ACTIVE);
+ break;
+ default:
+ break;
+ }
+ tile.updateTile();
+ }
+ }
+}
diff --git a/tests/rollback/AndroidTest.xml b/tests/rollback/AndroidTest.xml
index 534ed1e..0194557 100644
--- a/tests/rollback/AndroidTest.xml
+++ b/tests/rollback/AndroidTest.xml
@@ -24,7 +24,11 @@
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+ <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+ <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.C" />
<option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+ <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+ <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.C" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="com.android.cts.rollback"/>
diff --git a/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java b/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
index af23ff5..05c4ca7 100644
--- a/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
+++ b/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
@@ -21,17 +21,24 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
import android.Manifest;
+import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
import android.provider.DeviceConfig;
import androidx.test.InstrumentationRegistry;
import com.android.cts.install.lib.Install;
import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.LocalIntentSender;
import com.android.cts.install.lib.TestApp;
import com.android.cts.install.lib.Uninstall;
import com.android.cts.rollback.lib.Rollback;
+import com.android.cts.rollback.lib.RollbackBroadcastReceiver;
import com.android.cts.rollback.lib.RollbackUtils;
import org.junit.After;
@@ -41,12 +48,19 @@
import org.junit.runners.JUnit4;
import java.io.IOException;
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
/**
* CTS Tests for RollbackManager APIs.
*/
@RunWith(JUnit4.class)
public class RollbackManagerTest {
+ // TODO: use PackageManager.RollbackDataPolicy.* when they are system API
+ private static final int ROLLBACK_DATA_POLICY_RESTORE = 0;
+ private static final int ROLLBACK_DATA_POLICY_WIPE = 1;
+
+ private static final String PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS = "enable_rollback_timeout";
/**
* Adopts common permissions needed to test rollbacks and uninstalls the
@@ -58,11 +72,14 @@
.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.MANAGE_ROLLBACKS,
Manifest.permission.TEST_MANAGE_ROLLBACKS,
Manifest.permission.READ_DEVICE_CONFIG,
- Manifest.permission.WRITE_DEVICE_CONFIG);
+ Manifest.permission.WRITE_DEVICE_CONFIG,
+ Manifest.permission.FORCE_STOP_PACKAGES,
+ Manifest.permission.SET_TIME);
- Uninstall.packages(TestApp.A);
+ Uninstall.packages(TestApp.A, TestApp.B, TestApp.C);
}
/**
@@ -70,7 +87,7 @@
*/
@After
public void teardown() throws InterruptedException, IOException {
- Uninstall.packages(TestApp.A);
+ Uninstall.packages(TestApp.A, TestApp.B, TestApp.C);
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
@@ -110,12 +127,227 @@
assertThat(committed).causePackagesContainsExactly(TestApp.A2);
}
+ /**
+ * Tests rollback of multi-package installs is implemented.
+ */
+ @Test
+ public void testBasic_MultiPackage() throws Exception {
+ // Prep installation of the test apps.
+ Install.multi(TestApp.A1, TestApp.B1).commit();
+ Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+
+ // TestApp.A should now be available for rollback.
+ RollbackInfo rollback = RollbackUtils.waitForAvailableRollback(TestApp.A);
+ assertThat(rollback).isNotNull();
+ assertThat(rollback).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1),
+ Rollback.from(TestApp.B2).to(TestApp.B1));
+
+ // Rollback the app. It should cause both test apps to be rolled back.
+ RollbackUtils.rollback(rollback.getRollbackId());
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
+
+ // We should see recent rollbacks listed for both A and B.
+ RollbackInfo rollbackA = RollbackUtils.getCommittedRollback(TestApp.A);
+ RollbackInfo rollbackB = RollbackUtils.getCommittedRollback(TestApp.B);
+ assertThat(rollbackA).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1),
+ Rollback.from(TestApp.B2).to(TestApp.B1));
+ assertThat(rollbackA).hasRollbackId(rollback.getRollbackId());
+ assertThat(rollbackB).hasRollbackId(rollback.getRollbackId());
+ }
+
+ /**
+ * Tests rollbacks are properly persisted.
+ */
+ @Test
+ public void testSingleRollbackPersistence() throws Exception {
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ RollbackInfo rollbackA = RollbackUtils.waitForAvailableRollback(TestApp.A);
+ assertThat(rollbackA).isNotNull();
+
+ // Check the available rollback is persisted correctly
+ rm.reloadPersistedData();
+ rollbackA = RollbackUtils.getAvailableRollback(TestApp.A);
+ assertThat(rollbackA).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
+
+ // Rollback the app
+ TestApp cause = new TestApp("Foo", "com.android.tests.rollback.testapp.Foo",
+ /*versionCode*/ 42, /*isApex*/ false);
+ RollbackUtils.rollback(rollbackA.getRollbackId(), cause);
+ RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
+ assertThat(committed).isNotNull();
+
+ // Check the committed rollback is persisted correctly
+ rm.reloadPersistedData();
+ committed = RollbackUtils.getCommittedRollback(TestApp.A);
+ assertThat(committed).hasRollbackId(rollbackA.getRollbackId());
+ assertThat(committed).isNotStaged();
+ assertThat(committed).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(committed).causePackagesContainsExactly(cause);
+ }
+
+ /**
+ * Tests rollbacks are properly persisted.
+ */
+ @Test
+ public void testMultiRollbackPersistence() throws Exception {
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+
+ Install.multi(TestApp.A1, TestApp.B1).commit();
+ Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
+ RollbackInfo rollbackA = RollbackUtils.waitForAvailableRollback(TestApp.A);
+ RollbackInfo rollbackB = RollbackUtils.waitForAvailableRollback(TestApp.B);
+ assertThat(rollbackA).isNotNull();
+ assertThat(rollbackB).isNotNull();
+
+ // Check the available rollback is persisted correctly
+ rm.reloadPersistedData();
+ rollbackA = RollbackUtils.getAvailableRollback(TestApp.A);
+ rollbackB = RollbackUtils.getAvailableRollback(TestApp.B);
+ assertThat(rollbackB).hasRollbackId(rollbackA.getRollbackId());
+ assertThat(rollbackA).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1),
+ Rollback.from(TestApp.B2).to(TestApp.B1));
+
+ // Rollback the app
+ RollbackUtils.rollback(rollbackA.getRollbackId());
+ RollbackInfo committedA = RollbackUtils.getCommittedRollback(TestApp.A);
+ RollbackInfo committedB = RollbackUtils.getCommittedRollback(TestApp.B);
+ assertThat(committedA).isNotNull();
+ assertThat(committedB).isNotNull();
+
+ // Check the committed rollback is persisted correctly
+ rm.reloadPersistedData();
+ committedA = RollbackUtils.getCommittedRollback(TestApp.A);
+ committedB = RollbackUtils.getCommittedRollback(TestApp.B);
+ assertThat(committedA).hasRollbackId(rollbackA.getRollbackId());
+ assertThat(committedB).hasRollbackId(rollbackA.getRollbackId());
+ assertThat(committedA).isNotStaged();
+ assertThat(committedA).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1),
+ Rollback.from(TestApp.B2).to(TestApp.B1));
+ }
+
+ /**
+ * Tests that the MANAGE_ROLLBACKS permission is required to call
+ * RollbackManager APIs.
+ */
+ @Test
+ public void testManageRollbacksPermission() throws Exception {
+ // We shouldn't be allowed to call any of the RollbackManager APIs
+ // without the MANAGE_ROLLBACKS permission.
+ InstallUtils.dropShellPermissionIdentity();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+
+ try {
+ rm.getAvailableRollbacks();
+ fail("expected SecurityException");
+ } catch (SecurityException e) {
+ // Expected.
+ }
+
+ try {
+ rm.getRecentlyCommittedRollbacks();
+ fail("expected SecurityException");
+ } catch (SecurityException e) {
+ // Expected.
+ }
+
+ try {
+ LocalIntentSender sender = new LocalIntentSender();
+ rm.commitRollback(0, Collections.emptyList(), sender.getIntentSender());
+ fail("expected SecurityException");
+ } catch (SecurityException e) {
+ // Expected.
+ }
+
+ try {
+ rm.reloadPersistedData();
+ fail("expected SecurityException");
+ } catch (SecurityException e) {
+ // Expected.
+ }
+
+ try {
+ rm.expireRollbackForPackage(TestApp.A);
+ fail("expected SecurityException");
+ } catch (SecurityException e) {
+ // Expected.
+ }
+ }
+
+ /**
+ * Tests that you cannot enable rollback for a package without the
+ * MANAGE_ROLLBACKS permission.
+ */
+ @Test
+ public void testEnableRollbackPermission() throws Exception {
+ InstallUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES);
+
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+
+ // We expect v2 of the app was installed, but rollback has not
+ // been enabled.
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ InstallUtils.adoptShellPermissionIdentity(
+ Manifest.permission.MANAGE_ROLLBACKS,
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES);
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+ }
+
+ /**
+ * Tests that you cannot enable rollback for a non-module package when
+ * holding the MANAGE_ROLLBACKS permission without TEST_MANAGE_ROLLBACKS.
+ */
+ @Test
+ public void testNonModuleEnableRollback() throws Exception {
+ InstallUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.MANAGE_ROLLBACKS);
+
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+
+ // We expect v2 of the app was installed, but rollback has not
+ // been enabled because the test app is not a module.
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+ }
+
+ /**
+ * Tests failure to enable rollback for multi-package installs.
+ * If any one of the packages fail to enable rollback, we shouldn't enable
+ * rollback for any package.
+ */
+ @Test
+ public void testMultiPackageEnableFail() throws Exception {
+ Install.single(TestApp.A1).commit();
+ // We should fail to enable rollback here because TestApp B is not
+ // already installed.
+ Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
+
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.B)).isNull();
+ }
+
@Test
public void testGetRollbackDataPolicy() throws Exception {
- // TODO: To change to the following statement when
- // PackageManager.RollbackDataPolicy.WIPE is available.
- // final int rollBackDataPolicy = PackageManager.RollbackDataPolicy.WIPE;
- final int rollBackDataPolicy = 1;
+ final int rollBackDataPolicy = ROLLBACK_DATA_POLICY_WIPE;
Install.single(TestApp.A1).commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
@@ -181,4 +413,345 @@
RollbackUtils.rollback(available.getRollbackId(), TestApp.ARotated2);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
}
+
+ /**
+ * Test we can't enable rollback for non-allowlisted app without
+ * TEST_MANAGE_ROLLBACKS permission
+ */
+ @Test
+ public void testNonRollbackAllowlistedApp() throws Exception {
+ InstallUtils.dropShellPermissionIdentity();
+ InstallUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.MANAGE_ROLLBACKS);
+
+ Install.single(TestApp.A1).commit();
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ Thread.sleep(TimeUnit.SECONDS.toMillis(2));
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+ }
+
+ /**
+ * Tests user data is restored according to the preset rollback data policy.
+ */
+ @Test
+ public void testRollbackDataPolicy() throws Exception {
+ Install.multi(TestApp.A1, TestApp.B1, TestApp.C1).commit();
+ // Write user data version = 1
+ InstallUtils.processUserData(TestApp.A);
+ InstallUtils.processUserData(TestApp.B);
+ InstallUtils.processUserData(TestApp.C);
+
+ Install a2 = Install.single(TestApp.A2)
+ .setEnableRollback(PackageManager.ROLLBACK_DATA_POLICY_WIPE);
+ Install b2 = Install.single(TestApp.B2)
+ .setEnableRollback(PackageManager.ROLLBACK_DATA_POLICY_RESTORE);
+ Install c2 = Install.single(TestApp.C2)
+ .setEnableRollback(PackageManager.ROLLBACK_DATA_POLICY_RETAIN);
+ Install.multi(a2, b2, c2).setEnableRollback().commit();
+ // Write user data version = 2
+ InstallUtils.processUserData(TestApp.A);
+ InstallUtils.processUserData(TestApp.B);
+ InstallUtils.processUserData(TestApp.C);
+
+ RollbackInfo info = RollbackUtils.getAvailableRollback(TestApp.A);
+ RollbackUtils.rollback(info.getRollbackId());
+ // Read user data version from userdata.txt
+ // A's user data version is -1 for user data is wiped.
+ // B's user data version is 1 for user data is restored.
+ // C's user data version is 2 for user data is retained.
+ assertThat(InstallUtils.getUserDataVersion(TestApp.A)).isEqualTo(-1);
+ assertThat(InstallUtils.getUserDataVersion(TestApp.B)).isEqualTo(1);
+ assertThat(InstallUtils.getUserDataVersion(TestApp.C)).isEqualTo(2);
+ }
+
+ /**
+ * Tests user data is restored according to the rollback data policy defined in the manifest.
+ */
+ @Test
+ public void testRollbackDataPolicy_Manifest() throws Exception {
+ Install.multi(TestApp.A1, TestApp.B1, TestApp.C1).commit();
+ // Write user data version = 1
+ InstallUtils.processUserData(TestApp.A);
+ InstallUtils.processUserData(TestApp.B);
+ InstallUtils.processUserData(TestApp.C);
+
+ Install a2 = Install.single(TestApp.ARollbackWipe2).setEnableRollback();
+ Install b2 = Install.single(TestApp.BRollbackRestore2).setEnableRollback();
+ Install c2 = Install.single(TestApp.CRollbackRetain2).setEnableRollback();
+ Install.multi(a2, b2, c2).setEnableRollback().commit();
+ // Write user data version = 2
+ InstallUtils.processUserData(TestApp.A);
+ InstallUtils.processUserData(TestApp.B);
+ InstallUtils.processUserData(TestApp.C);
+
+ RollbackInfo info = RollbackUtils.getAvailableRollback(TestApp.A);
+ RollbackUtils.rollback(info.getRollbackId());
+ // Read user data version from userdata.txt
+ // A's user data version is -1 for user data is wiped.
+ // B's user data version is 1 for user data is restored.
+ // C's user data version is 2 for user data is retained.
+ assertThat(InstallUtils.getUserDataVersion(TestApp.A)).isEqualTo(-1);
+ assertThat(InstallUtils.getUserDataVersion(TestApp.B)).isEqualTo(1);
+ assertThat(InstallUtils.getUserDataVersion(TestApp.C)).isEqualTo(2);
+ }
+
+ /**
+ * Test explicit expiration of rollbacks.
+ * Does not test the scheduling aspects of rollback expiration.
+ */
+ @Test
+ public void testRollbackExpiration() throws Exception {
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ RollbackUtils.waitForAvailableRollback(TestApp.A);
+
+ // Expire the rollback.
+ RollbackUtils.getRollbackManager().expireRollbackForPackage(TestApp.A);
+
+ // The rollback should no longer be available.
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+ }
+
+ /**
+ * Test restrictions on rollback broadcast sender.
+ * A random app should not be able to send a ROLLBACK_COMMITTED broadcast.
+ */
+ @Test
+ public void testRollbackBroadcastRestrictions() throws Exception {
+ RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
+ Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
+ try {
+ InstrumentationRegistry.getContext().sendBroadcast(broadcast);
+ fail("Succeeded in sending restricted broadcast from app context.");
+ } catch (SecurityException se) {
+ // Expected behavior.
+ }
+
+ // Confirm that we really haven't received the broadcast.
+ // TODO: How long to wait for the expected timeout?
+ assertThat(broadcastReceiver.poll(5, TimeUnit.SECONDS)).isNull();
+
+ // TODO: Do we need to do this? Do we need to ensure this is always
+ // called, even when the test fails?
+ broadcastReceiver.unregister();
+ }
+
+ /**
+ * Test rollback of apks involving splits.
+ */
+ @Test
+ public void testRollbackWithSplits() throws Exception {
+ Install.single(TestApp.ASplit1).commit();
+ Install.single(TestApp.ASplit2).setEnableRollback().commit();
+ RollbackInfo rollback = RollbackUtils.waitForAvailableRollback(TestApp.A);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+ RollbackUtils.rollback(rollback.getRollbackId());
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ }
+
+ /**
+ * Test bad update automatic rollback.
+ */
+ @Test
+ public void testBadUpdateRollback() throws Exception {
+ // Prep installation of the test apps.
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.ACrashing2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+ Install.single(TestApp.B1).commit();
+ Install.single(TestApp.B2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+
+ // Both test apps should now be available for rollback, and the
+ // targetPackage returned for rollback should be correct.
+ RollbackInfo rollbackA = RollbackUtils.waitForAvailableRollback(TestApp.A);
+ assertThat(rollbackA).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
+ RollbackInfo rollbackB = RollbackUtils.waitForAvailableRollback(TestApp.B);
+ assertThat(rollbackB).packagesContainsExactly(Rollback.from(TestApp.B2).to(TestApp.B1));
+
+ // Register rollback committed receiver
+ RollbackBroadcastReceiver rollbackReceiver = new RollbackBroadcastReceiver();
+
+ // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
+ RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
+
+ // Verify we received a broadcast for the rollback.
+ assertThat(rollbackReceiver.poll(1, TimeUnit.MINUTES)).isNotNull();
+
+ // TestApp.A is automatically rolled back by the RollbackPackageHealthObserver
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ // Instrumented app is still the package installer
+ String installer = InstrumentationRegistry.getContext().getPackageManager()
+ .getInstallerPackageName(TestApp.A);
+ assertThat(installer).isEqualTo(getClass().getPackageName());
+ // TestApp.B is untouched
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+ }
+
+ /**
+ * Tests we fail to enable rollbacks if enable-rollback times out.
+ */
+ @Test
+ public void testEnableRollbackTimeoutFailsRollback() throws Exception {
+ //setting the timeout to a very short amount that will definitely be triggered
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS,
+ Long.toString(0), false /* makeDefault*/);
+
+ try {
+ Install.single(TestApp.A1).commit();
+ RollbackUtils.waitForUnavailableRollback(TestApp.A);
+
+ // Block the RollbackManager to make extra sure it will not be
+ // able to enable the rollback in time.
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(1));
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+ // Give plenty of time for RollbackManager to unblock and attempt
+ // to make the rollback available before asserting that the
+ // rollback was not made available.
+ Thread.sleep(TimeUnit.SECONDS.toMillis(2));
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+ } finally {
+ //setting the timeout back to default
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS,
+ null, false /* makeDefault*/);
+ }
+ }
+
+ /**
+ * Tests we fail to enable rollbacks if enable-rollback times out for any child session.
+ */
+ @Test
+ public void testEnableRollbackTimeoutFailsRollback_MultiPackage() throws Exception {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS,
+ Long.toString(5000), false /* makeDefault*/);
+
+ try {
+ Install.multi(TestApp.A1, TestApp.B1).commit();
+ RollbackUtils.waitForUnavailableRollback(TestApp.A);
+
+ // Block the 2nd session for 10s so it will not be able to enable the rollback in time.
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(0));
+ rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(10));
+ Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+
+ // Give plenty of time for RollbackManager to unblock and attempt
+ // to make the rollback available before asserting that the
+ // rollback was not made available.
+ Thread.sleep(TimeUnit.SECONDS.toMillis(2));
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.B)).isNull();
+ } finally {
+ //setting the timeout back to default
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS,
+ null, false /* makeDefault*/);
+ }
+ }
+
+ /**
+ * Test the scheduling aspect of rollback expiration.
+ */
+ @Test
+ public void testRollbackExpiresAfterLifetime() throws Exception {
+ long expirationTime = TimeUnit.SECONDS.toMillis(30);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
+ RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
+ Long.toString(expirationTime), false /* makeDefault*/);
+
+ try {
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ RollbackUtils.waitForAvailableRollback(TestApp.A);
+
+ // Give it a little more time, but still not long enough to expire
+ Thread.sleep(expirationTime / 2);
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNotNull();
+
+ // Check that the data has expired after the expiration time (with a buffer of 1 second)
+ Thread.sleep(expirationTime / 2 + TimeUnit.SECONDS.toMillis(1));
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+ } finally {
+ // Restore default config values
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
+ RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
+ null, false /* makeDefault*/);
+ }
+ }
+
+ /**
+ * Test that available rollbacks should expire correctly when the property
+ * {@link RollbackManager#PROPERTY_ROLLBACK_LIFETIME_MILLIS} is changed
+ */
+ @Test
+ public void testRollbackExpiresWhenLifetimeChanges() throws Exception {
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ RollbackUtils.waitForAvailableRollback(TestApp.A);
+
+ // Change the lifetime to 0 which should expire rollbacks immediately
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
+ RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
+ Long.toString(0), false /* makeDefault*/);
+
+ try {
+ RollbackUtils.waitForUnavailableRollback(TestApp.A);
+ } finally {
+ // Restore default config values
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
+ RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
+ null, false /* makeDefault*/);
+ }
+ }
+
+ /**
+ * Test that changing time on device does not affect the duration of time that we keep
+ * rollback available
+ */
+ @Test
+ public void testTimeChangeDoesNotAffectLifetime() throws Exception {
+ long expirationTime = TimeUnit.SECONDS.toMillis(30);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
+ RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
+ Long.toString(expirationTime), false /* makeDefault*/);
+
+ try {
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ RollbackUtils.waitForAvailableRollback(TestApp.A);
+ RollbackUtils.forwardTimeBy(expirationTime);
+
+ try {
+ // The rollback should be still available after forwarding time
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNotNull();
+ // The rollback shouldn't expire when half of the expiration time elapses
+ Thread.sleep(expirationTime / 2);
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNotNull();
+ // The rollback now should expire
+ Thread.sleep(expirationTime / 2 + TimeUnit.SECONDS.toMillis(1));
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+ } finally {
+ RollbackUtils.forwardTimeBy(-expirationTime);
+ }
+ } finally {
+ // Restore default config values
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
+ RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
+ null, false /* makeDefault*/);
+ }
+ }
}
diff --git a/tests/rotationresolverservice/src/android/rotationresolverservice/cts/CtsRotationResolverServiceDeviceTest.java b/tests/rotationresolverservice/src/android/rotationresolverservice/cts/CtsRotationResolverServiceDeviceTest.java
index 7576ae8..8ab556c 100644
--- a/tests/rotationresolverservice/src/android/rotationresolverservice/cts/CtsRotationResolverServiceDeviceTest.java
+++ b/tests/rotationresolverservice/src/android/rotationresolverservice/cts/CtsRotationResolverServiceDeviceTest.java
@@ -62,6 +62,7 @@
private static final boolean FAKE_SHOULD_USE_CAMERA = true;
private static final long FAKE_TIME_OUT = 1000L;
private static final long TEMPORARY_SERVICE_DURATION = 5000L;
+ private static final String USER_ID = "0";
private final boolean isTestable =
!TextUtils.isEmpty(getRotationResolverServiceComponent());
@@ -150,7 +151,7 @@
}
private String getRotationResolverServiceComponent() {
- return runShellCommand("cmd resolver get-bound-package");
+ return runShellCommand("cmd resolver get-bound-package %s", USER_ID);
}
private int getLastTestCallbackCode() {
@@ -164,16 +165,17 @@
* in our test service w/ CountDownLatch(s).
*/
private void callResolveRotation() {
- runShellCommand("cmd resolver resolve-rotation");
+ runShellCommand("cmd resolver resolve-rotation %s", USER_ID);
CtsTestRotationResolverService.onReceivedResponse();
}
private void setTestableRotationResolverService(String service) {
- runShellCommand("cmd resolver set-temporary-service %s %s", service, TEMPORARY_SERVICE_DURATION);
+ runShellCommand("cmd resolver set-temporary-service %s %s %s",
+ USER_ID, service, TEMPORARY_SERVICE_DURATION);
}
private void clearTestableRotationResolverService() {
- runShellCommand("cmd resolver set-temporary-service");
+ runShellCommand("cmd resolver set-temporary-service %s", USER_ID);
}
}
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
index 74f28ba..8bc910d 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
@@ -85,7 +85,8 @@
acquireWakeLock();
}
};
- mContext.registerReceiver(receiver, intentFilter);
+ mContext.registerReceiver(receiver, intentFilter,
+ Context.RECEIVER_NOT_EXPORTED);
AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
long wakeupTimeMs = (System.currentTimeMillis()
diff --git a/tests/signature/intent-check/DynamicConfig.xml b/tests/signature/intent-check/DynamicConfig.xml
index 59fe44d..238d445 100644
--- a/tests/signature/intent-check/DynamicConfig.xml
+++ b/tests/signature/intent-check/DynamicConfig.xml
@@ -26,9 +26,6 @@
Bug: 150153196 android.intent.action.LOAD_DATA (system in API 30)
Bug: 150153196 android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY (system in API 30)
Bug: 186495404 android.intent.action.REBOOT_READY
- Bug: 206897736 android.intent.action.MANAGE_PERMISSION_USAGE
- Bug: 206897736 android.intent.action.VIEW_APP_FEATURES
- Bug: 209528070 android.intent.action.APPLICATION_LOCALE_CHANGED
-->
<dynamicConfig>
<entry key ="intent_whitelist">
@@ -43,8 +40,5 @@
<value>android.intent.action.LOAD_DATA</value>
<value>android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY</value>
<value>android.intent.action.REBOOT_READY</value>
- <value>android.intent.action.MANAGE_PERMISSION_USAGE</value>
- <value>android.intent.action.VIEW_APP_FEATURES</value>
- <value>android.intent.action.APPLICATION_LOCALE_CHANGED</value>
</entry>
</dynamicConfig>
diff --git a/tests/signature/lib/android/src/android/signature/cts/DexMemberChecker.java b/tests/signature/lib/android/src/android/signature/cts/DexMemberChecker.java
index fa839af..328ed39 100644
--- a/tests/signature/lib/android/src/android/signature/cts/DexMemberChecker.java
+++ b/tests/signature/lib/android/src/android/signature/cts/DexMemberChecker.java
@@ -17,7 +17,7 @@
package android.signature.cts;
import android.util.Log;
-import java.lang.reflect.Constructor;
+
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@@ -97,11 +97,8 @@
if (jni) {
try {
observer.fieldAccessibleViaJni(hasMatchingField_JNI(klass, field), field);
- } catch (ClassNotFoundException | ExceptionInInitializerError | UnsatisfiedLinkError
- | NoClassDefFoundError e) {
- if ((e instanceof NoClassDefFoundError)
- && !(e.getCause() instanceof ExceptionInInitializerError)
- && !(e.getCause() instanceof UnsatisfiedLinkError)) {
+ } catch (ClassNotFoundException | Error e) {
+ if ((e instanceof NoClassDefFoundError) && !(e.getCause() instanceof Error)) {
throw (NoClassDefFoundError) e;
}
@@ -122,11 +119,8 @@
if (jni) {
try {
observer.methodAccessibleViaJni(hasMatchingMethod_JNI(klass, method), method);
- } catch (ExceptionInInitializerError | UnsatisfiedLinkError
- | NoClassDefFoundError e) {
- if ((e instanceof NoClassDefFoundError)
- && !(e.getCause() instanceof ExceptionInInitializerError)
- && !(e.getCause() instanceof UnsatisfiedLinkError)) {
+ } catch (Error e) {
+ if ((e instanceof NoClassDefFoundError) && !(e.getCause() instanceof Error)) {
throw e;
}
diff --git a/tests/signature/lib/common/src/android/signature/cts/ApiComplianceChecker.java b/tests/signature/lib/common/src/android/signature/cts/ApiComplianceChecker.java
index fe67157..e5e6c84 100644
--- a/tests/signature/lib/common/src/android/signature/cts/ApiComplianceChecker.java
+++ b/tests/signature/lib/common/src/android/signature/cts/ApiComplianceChecker.java
@@ -22,7 +22,9 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Formatter;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -48,6 +50,46 @@
+ "int,android.telephony.euicc.DownloadableSubscription,boolean,boolean)");
}
+ /**
+ * A set of field values signatures whose value modifier should be ignored.
+ *
+ * <p>If a field value is intended to be changed to correct its value, that change should be
+ * allowed. The field name is the key of the ignoring map, and a FieldValuePair which is a pair
+ * of the old value and the new value is the value of the ignoring map.
+ * WARNING: Entries should only be added after consulting API council.
+ */
+ private static class FieldValuePair {
+ private String oldValue;
+ private String newValue;
+
+ private FieldValuePair(String oldValue, String newValue) {
+ this.oldValue = oldValue;
+ this.newValue = newValue;
+ }
+ };
+ private static final Map<String, FieldValuePair> IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST =
+ new HashMap<String, FieldValuePair>();
+ static {
+ // This field value was previously wrong. As the CtsSystemApiSignatureTestCases package
+ // tests both the old and new specifications with both old and new values, this needs to be
+ // ignored.
+ IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put(
+ "android.media.tv.tuner.frontend.FrontendSettings#FEC_28_45(long)",
+ new FieldValuePair("-2147483648", "2147483648"));
+ IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put(
+ "android.media.tv.tuner.frontend.FrontendSettings#FEC_29_45(long)",
+ new FieldValuePair("1", "4294967296"));
+ IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put(
+ "android.media.tv.tuner.frontend.FrontendSettings#FEC_31_45(long)",
+ new FieldValuePair("2", "8589934592"));
+ IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put(
+ "android.media.tv.tuner.frontend.FrontendSettings#FEC_32_45(long)",
+ new FieldValuePair("4", "17179869184"));
+ IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put(
+ "android.media.tv.tuner.frontend.FrontendSettings#FEC_77_90(long)",
+ new FieldValuePair("8", "34359738368"));
+ }
+
/** Indicates that the class is an annotation. */
private static final int CLASS_MODIFIER_ANNOTATION = 0x00002000;
@@ -311,7 +353,7 @@
expectedFieldType, actualFieldType));
}
- String message = checkFieldValueCompliance(fieldDescription, field);
+ String message = checkFieldValueCompliance(classDescription, fieldDescription, field);
if (message != null) {
resultObserver.notifyFailure(FailureType.MISMATCH_FIELD,
fieldDescription.toReadableString(classDescription.getAbsoluteClassName()),
@@ -363,7 +405,8 @@
* @param apiField The field as defined by the platform API.
* @param deviceField The field as defined by the device under test.
*/
- private static String checkFieldValueCompliance(JDiffField apiField, Field deviceField) {
+ private static String checkFieldValueCompliance(
+ JDiffClassDescription classDescription, JDiffField apiField, Field deviceField) {
if ((apiField.mModifier & Modifier.FINAL) == 0 ||
(apiField.mModifier & Modifier.STATIC) == 0) {
// Only final static fields can have fixed values.
@@ -383,6 +426,14 @@
String deviceFieldValue = getFieldValueAsString(deviceField);
if (!Objects.equals(apiFieldValue, deviceFieldValue)) {
+ String fieldName = apiField.toReadableString(classDescription.getAbsoluteClassName());
+ if (IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.containsKey(fieldName)
+ && IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.get(fieldName).oldValue.equals(
+ apiFieldValue)
+ && IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.get(fieldName).newValue.equals(
+ deviceFieldValue)) {
+ return null;
+ }
return String.format("Incorrect field value, expected <%s>, found <%s>",
apiFieldValue, deviceFieldValue);
diff --git a/tests/tests/accounts/OWNERS b/tests/tests/accounts/OWNERS
index 695530b..0ca6900 100644
--- a/tests/tests/accounts/OWNERS
+++ b/tests/tests/accounts/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 82728
-carlosvaldivia@google.com
-dementyev@google.com
-sandrakwan@google.com
aseemk@google.com
+dementyev@google.com
+mpape@google.com
diff --git a/tests/tests/activityrecognition/AndroidTest.xml b/tests/tests/activityrecognition/AndroidTest.xml
index 86536b7..b4bce63 100644
--- a/tests/tests/activityrecognition/AndroidTest.xml
+++ b/tests/tests/activityrecognition/AndroidTest.xml
@@ -21,6 +21,7 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
diff --git a/tests/tests/app.usage/Android.bp b/tests/tests/app.usage/Android.bp
index 29ba591..79cb676 100644
--- a/tests/tests/app.usage/Android.bp
+++ b/tests/tests/app.usage/Android.bp
@@ -26,6 +26,7 @@
"ctstestrunner-axt",
"cts-wm-util",
"junit",
+ "permission-test-util-lib",
"ub-uiautomator",
],
libs: [
diff --git a/tests/tests/app.usage/AndroidManifest.xml b/tests/tests/app.usage/AndroidManifest.xml
index 141a9af..75272ee 100644
--- a/tests/tests/app.usage/AndroidManifest.xml
+++ b/tests/tests/app.usage/AndroidManifest.xml
@@ -28,6 +28,7 @@
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config">
diff --git a/tests/tests/app.usage/TestApp1/aidl/android/app/usage/cts/ITestReceiver.aidl b/tests/tests/app.usage/TestApp1/aidl/android/app/usage/cts/ITestReceiver.aidl
index d16a12b..e7a374c 100644
--- a/tests/tests/app.usage/TestApp1/aidl/android/app/usage/cts/ITestReceiver.aidl
+++ b/tests/tests/app.usage/TestApp1/aidl/android/app/usage/cts/ITestReceiver.aidl
@@ -17,4 +17,5 @@
interface ITestReceiver {
boolean isAppInactive(String pkg);
+ void generateAndSendNotification();
}
\ No newline at end of file
diff --git a/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/TestService.java b/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/TestService.java
index eb34e95..bfe475f 100644
--- a/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/TestService.java
+++ b/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/TestService.java
@@ -16,14 +16,23 @@
package android.app.usage.cts.test1;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.app.Service;
import android.app.usage.UsageStatsManager;
import android.app.usage.cts.ITestReceiver;
-import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
+import android.provider.Settings;
public class TestService extends Service {
+ private static final String TEST_CHANNEL_ID = "test_channel_id";
+ private static final String TEST_CHANNEL_NAME = "test_channel_name";
+ private static final String TEST_CHANNEL_DESC = "Test channel";
+ private static final int TEST_NOTIFICATION_ID = 11;
+
@Override
public IBinder onBind(Intent intent) {
return new TestReceiver();
@@ -32,9 +41,28 @@
private class TestReceiver extends ITestReceiver.Stub {
@Override
public boolean isAppInactive(String pkg) {
- UsageStatsManager usm = (UsageStatsManager) getSystemService(
- Context.USAGE_STATS_SERVICE);
+ UsageStatsManager usm = getSystemService(UsageStatsManager.class);
return usm.isAppInactive(pkg);
}
+
+ @Override
+ public void generateAndSendNotification() {
+ final NotificationManager notificationManager =
+ getSystemService(NotificationManager.class);
+ final NotificationChannel mChannel = new NotificationChannel(TEST_CHANNEL_ID,
+ TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
+ // Configure the notification channel.
+ mChannel.setDescription(TEST_CHANNEL_DESC);
+ notificationManager.createNotificationChannel(mChannel);
+ final Notification.Builder mBuilder =
+ new Notification.Builder(getApplicationContext(), TEST_CHANNEL_ID)
+ .setSmallIcon(android.R.drawable.ic_info)
+ .setContentTitle("My notification")
+ .setContentText("Hello World!");
+ final PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 1,
+ new Intent(Settings.ACTION_SETTINGS), PendingIntent.FLAG_IMMUTABLE);
+ mBuilder.setContentIntent(pi);
+ notificationManager.notify(TEST_NOTIFICATION_ID, mBuilder.build());
+ }
}
}
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/TestJob.java b/tests/tests/app.usage/src/android/app/usage/cts/TestJob.java
index decc9d8..0efa65f 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/TestJob.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/TestJob.java
@@ -23,19 +23,14 @@
import android.content.ComponentName;
import android.content.Context;
-import java.util.function.BooleanSupplier;
+import java.util.function.Supplier;
public final class TestJob extends JobService {
public static final int TEST_JOB_ID = 1;
public static final String NOTIFICATION_CHANNEL_ID = TestJob.class.getSimpleName();
private static boolean sJobStarted;
- public static BooleanSupplier hasJobStarted = new BooleanSupplier() {
- @Override
- public boolean getAsBoolean() {
- return sJobStarted;
- }
- };
+ public static Supplier<Boolean> hasJobStarted = () -> sJobStarted;
@Override
public boolean onStartJob(JobParameters params) {
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
index a20a8f9..ee0d10e 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
@@ -16,6 +16,12 @@
package android.app.usage.cts;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
+import static android.provider.DeviceConfig.NAMESPACE_APP_STANDBY;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -49,9 +55,12 @@
import android.net.Uri;
import android.os.IBinder;
import android.os.Parcel;
+import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.AppModeInstant;
import android.provider.Settings;
@@ -71,6 +80,8 @@
import com.android.compatibility.common.util.AppStandbyUtils;
import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
+import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.SystemUtil;
import org.junit.After;
@@ -86,11 +97,12 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
-import java.util.function.BooleanSupplier;
+import java.util.function.Supplier;
/**
* Test the UsageStats API. It is difficult to test the entire surface area
@@ -140,6 +152,14 @@
private static final ComponentName TEST_APP2_PIP_COMPONENT = new ComponentName(TEST_APP2_PKG,
TEST_APP2_CLASS_PIP);
+ // TODO(206518483): Define these constants in UsageStatsManager to avoid hardcoding here.
+ private static final String KEY_NOTIFICATION_SEEN_HOLD_DURATION =
+ "notification_seen_duration";
+ private static final String KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET =
+ "notification_seen_promoted_bucket";
+
+ private static final int DEFAULT_TIMEOUT_MS = 10_000;
+
private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
private static final long MINUTE = TimeUnit.MINUTES.toMillis(1);
private static final long DAY = TimeUnit.DAYS.toMillis(1);
@@ -173,6 +193,7 @@
Context.USAGE_STATS_SERVICE);
mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
mTargetPackage = mContext.getPackageName();
+ PermissionUtils.grantPermission(mTargetPackage, POST_NOTIFICATIONS);
mWMStateHelper = new WindowManagerStateHelper();
@@ -200,6 +221,14 @@
removeUser(mOtherUser);
mOtherUser = 0;
}
+ // Use test API to prevent PermissionManager from killing the test process when revoking
+ // permission.
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mContext.getSystemService(PermissionManager.class)
+ .revokePostNotificationPermissionWithoutKillForTest(
+ mTargetPackage,
+ Process.myUserHandle().getIdentifier()),
+ REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL);
}
private static void assertLessThan(long left, long right) {
@@ -259,6 +288,19 @@
@AppModeFull(reason = "No usage events access in instant apps")
@Test
+ public void testLastTimeVisible_launchActivityShouldBeDetected() throws Exception {
+ mUiDevice.wakeUp();
+ dismissKeyguard(); // also want to start out with the keyguard dismissed.
+
+ final long startTime = System.currentTimeMillis();
+ launchSubActivity(Activities.ActivityOne.class);
+ final long endTime = System.currentTimeMillis();
+
+ verifyLastTimeVisibleWithinRange(startTime, endTime, mTargetPackage);
+ }
+
+ @AppModeFull(reason = "No usage events access in instant apps")
+ @Test
public void testLastTimeAnyComponentUsed_launchActivityShouldBeDetected() throws Exception {
mUiDevice.wakeUp();
dismissKeyguard(); // also want to start out with the keyguard dismissed.
@@ -311,6 +353,17 @@
verifyLastTimeAnyComponentUsedWithinRange(startTime, endTime, TEST_APP_PKG);
}
+ private void verifyLastTimeVisibleWithinRange(
+ long startTime, long endTime, String targetPackage) {
+ final Map<String, UsageStats> map = mUsageStatsManager.queryAndAggregateUsageStats(
+ startTime, endTime);
+ final UsageStats stats = map.get(targetPackage);
+ assertNotNull(stats);
+ final long lastTimeVisible = stats.getLastTimeVisible();
+ assertLessThanOrEqual(startTime, lastTimeVisible);
+ assertLessThanOrEqual(lastTimeVisible, endTime);
+ }
+
private void verifyLastTimeAnyComponentUsedWithinRange(
long startTime, long endTime, String targetPackage) {
final Map<String, UsageStats> map = mUsageStatsManager.queryAndAggregateUsageStats(
@@ -475,7 +528,10 @@
startTime, endTime);
stats = events.get(mTargetPackage);
assertEquals(startingCount + 1, stats.getAppLaunchCount());
- mUiDevice.pressHome();
+
+ // Launch a new activity so the other sub activities go into a paused state.
+ launchTestActivity(TEST_APP_PKG, TEST_APP_CLASS);
+
launchSubActivity(Activities.ActivityOne.class);
launchSubActivity(Activities.ActivityTwo.class);
launchSubActivity(Activities.ActivityThree.class);
@@ -501,7 +557,7 @@
UsageEvents.Event event = new UsageEvents.Event();
assertTrue(events.getNextEvent(event));
if (event.getEventType() == UsageEvents.Event.STANDBY_BUCKET_CHANGED) {
- found |= event.getAppStandbyBucket() == UsageStatsManager.STANDBY_BUCKET_RARE;
+ found |= event.getAppStandbyBucket() == STANDBY_BUCKET_RARE;
}
}
@@ -521,7 +577,7 @@
assertTrue("No bucket data returned", bucketMap.size() > 0);
final int bucket = bucketMap.getOrDefault(mTargetPackage, -1);
assertEquals("Incorrect bucket returned for " + mTargetPackage, bucket,
- UsageStatsManager.STANDBY_BUCKET_RARE);
+ STANDBY_BUCKET_RARE);
} finally {
AppStandbyUtils.setAppStandbyEnabledAtRuntime(origValue);
}
@@ -554,7 +610,7 @@
numEvents++;
assertEquals("Event for a different package", mTargetPackage, event.getPackageName());
if (event.getEventType() == Event.STANDBY_BUCKET_CHANGED) {
- if (event.getAppStandbyBucket() == UsageStatsManager.STANDBY_BUCKET_RARE) {
+ if (event.getAppStandbyBucket() == STANDBY_BUCKET_RARE) {
rareTimeStamp = event.getTimeStamp();
}
else if (event.getAppStandbyBucket() == UsageStatsManager
@@ -736,7 +792,7 @@
public void testNotificationSeen() throws Exception {
final long startTime = System.currentTimeMillis();
- // Skip the test for wearable devices, televisions and automotives; neither has
+ // Skip the test for wearable devices, televisions and automotives; none of them have
// a notification shade, as notifications are shown via a different path than phones
assumeFalse("Test cannot run on a watch- notification shade is not shown",
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
@@ -781,10 +837,56 @@
@AppModeFull(reason = "No usage events access in instant apps")
@Test
+ public void testNotificationSeen_verifyBucket() throws Exception {
+ // Skip the test for wearable devices, televisions and automotives; none of them have
+ // a notification shade, as notifications are shown via a different path than phones
+ assumeFalse("Test cannot run on a watch- notification shade is not shown",
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
+ assumeFalse("Test cannot run on a television- notifications are not shown",
+ mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_LEANBACK_ONLY));
+ assumeFalse("Test cannot run on an automotive - notification shade is not shown",
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
+
+ final long promotedBucketHoldDurationMs = TimeUnit.MINUTES.toMillis(2);
+ try (DeviceConfigStateHelper deviceConfigStateHelper =
+ new DeviceConfigStateHelper(NAMESPACE_APP_STANDBY)) {
+ deviceConfigStateHelper.set(KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET,
+ String.valueOf(STANDBY_BUCKET_FREQUENT));
+ deviceConfigStateHelper.set(KEY_NOTIFICATION_SEEN_HOLD_DURATION,
+ String.valueOf(promotedBucketHoldDurationMs));
+
+ mUiDevice.wakeUp();
+ dismissKeyguard();
+ final TestServiceConnection connection = bindToTestServiceAndGetConnection();
+ try {
+ ITestReceiver testReceiver = connection.getITestReceiver();
+ testReceiver.generateAndSendNotification();
+ } finally {
+ connection.unbind();
+ }
+ setStandByBucket(TEST_APP_PKG, "rare");
+ waitUntil(() -> mUsageStatsManager.getAppStandbyBucket(TEST_APP_PKG),
+ STANDBY_BUCKET_RARE);
+ mUiDevice.openNotification();
+ waitUntil(() -> mUsageStatsManager.getAppStandbyBucket(TEST_APP_PKG),
+ STANDBY_BUCKET_FREQUENT);
+ // TODO(206518483): Verify the behavior after the promoted duration expires (which
+ // currently doesn't work as expected).
+ // SystemClock.sleep(promotedBucketHoldDurationMs);
+ // assertEquals(STANDBY_BUCKET_RARE, mUsageStatsManager.getAppStandbyBucket(
+ // TEST_APP_PKG));
+ mUiDevice.pressHome();
+ }
+ }
+
+ @AppModeFull(reason = "No usage events access in instant apps")
+ @Test
public void testNotificationInterruptionEventsObfuscation() throws Exception {
final long startTime = System.currentTimeMillis();
- // Skip the test for wearable devices and televisions; neither has a notification shade.
+ // Skip the test for wearable devices and televisions; none of them have a
+ // notification shade.
assumeFalse("Test cannot run on a watch- notification shade is not shown",
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
assumeFalse("Test cannot run on a television- notifications are not shown",
@@ -1099,16 +1201,10 @@
return events;
}
- private void waitUntil(BooleanSupplier condition, boolean expected) throws Exception {
- final long sleepTimeMs = 500;
- final int count = 10;
- for (int i = 0; i < count; ++i) {
- if (condition.getAsBoolean() == expected) {
- return;
- }
- Thread.sleep(sleepTimeMs);
- }
- fail("Condition wasn't satisfied after " + (sleepTimeMs * count) + "ms");
+ private <T> void waitUntil(Supplier<T> resultSupplier, T expectedResult) throws Exception {
+ final T actualResult = PollingCheck.waitFor(DEFAULT_TIMEOUT_MS, resultSupplier,
+ result -> Objects.equals(expectedResult, result));
+ assertEquals(expectedResult, actualResult);
}
static class AggrEventData {
@@ -1832,11 +1928,16 @@
}
private ITestReceiver bindToTestService() throws Exception {
+ final TestServiceConnection connection = bindToTestServiceAndGetConnection();
+ return connection.getITestReceiver();
+ }
+
+ private TestServiceConnection bindToTestServiceAndGetConnection() throws Exception {
final TestServiceConnection connection = new TestServiceConnection();
final Intent intent = new Intent().setComponent(
new ComponentName(TEST_APP_PKG, TEST_APP_CLASS_SERVICE));
mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
- return ITestReceiver.Stub.asInterface(connection.getService());
+ return connection;
}
/**
@@ -1900,6 +2001,14 @@
TimeUnit.SECONDS);
return service;
}
+
+ public ITestReceiver getITestReceiver() throws Exception {
+ return ITestReceiver.Stub.asInterface(getService());
+ }
+
+ public void unbind() {
+ mContext.unbindService(this);
+ }
}
private void runJobImmediately() throws Exception {
diff --git a/tests/tests/appenumeration/Android.bp b/tests/tests/appenumeration/Android.bp
index 5d510eb..cb6df44 100644
--- a/tests/tests/appenumeration/Android.bp
+++ b/tests/tests/appenumeration/Android.bp
@@ -30,6 +30,7 @@
"androidx.test.ext.junit",
"hamcrest-library",
"CtsAppEnumerationTestLib",
+ "cts-install-lib",
],
srcs: ["src/**/*.java"],
diff --git a/tests/tests/appenumeration/AndroidManifest.xml b/tests/tests/appenumeration/AndroidManifest.xml
index 9018766..a71c273 100644
--- a/tests/tests/appenumeration/AndroidManifest.xml
+++ b/tests/tests/appenumeration/AndroidManifest.xml
@@ -17,9 +17,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.appenumeration.cts">
-
- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
-
+ <!-- Use specific package names instead of QUERY_ALL_PACKAGES to let AppEnumerationTests
+ live with default visibility -->
+ <queries>
+ <package android:name="android.appenumeration.filters" />
+ <package android:name="android.appenumeration.noapi" />
+ <package android:name="android.appenumeration.noapi.shareduid" />
+ <package android:name="android.appenumeration.queries.activity.action" />
+ <package android:name="android.appenumeration.queries.nothing" />
+ <package android:name="android.appenumeration.stub" />
+ </queries>
<application>
<uses-library android:name="android.test.runner" />
</application>
diff --git a/tests/tests/appenumeration/AndroidTest.xml b/tests/tests/appenumeration/AndroidTest.xml
index d73484d..f2cd10d 100644
--- a/tests/tests/appenumeration/AndroidTest.xml
+++ b/tests/tests/appenumeration/AndroidTest.xml
@@ -35,6 +35,7 @@
<option name="test-file-name" value="CtsAppEnumerationDocumentsActivityTarget.apk" />
<option name="test-file-name" value="CtsAppEnumerationShareActivityTarget.apk" />
<option name="test-file-name" value="CtsAppEnumerationWebActivityTarget.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationPrefixWildcardWebActivityTarget.apk" />
<option name="test-file-name" value="CtsAppEnumerationBrowserActivityTarget.apk" />
<option name="test-file-name" value="CtsAppEnumerationBrowserWildcardActivityTarget.apk" />
<option name="test-file-name" value="CtsAppEnumerationSharedUidSource.apk" />
@@ -52,6 +53,7 @@
<option name="test-file-name" value="CtsAppEnumerationQueriesUnexportedProviderViaAuthority.apk" />
<option name="test-file-name" value="CtsAppEnumerationQueriesUnexportedProviderViaAction.apk" />
<option name="test-file-name" value="CtsAppEnumerationQueriesPackage.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationQueriesPackageHasProvider.apk" />
<option name="test-file-name" value="CtsAppEnumerationQueriesNothingTargetsQ.apk" />
<option name="test-file-name" value="CtsAppEnumerationQueriesNothingHasPermission.apk" />
<option name="test-file-name" value="CtsAppEnumerationQueriesNothingUsesLibrary.apk" />
@@ -68,6 +70,8 @@
<option name="test-file-name" value="CtsAppEnumerationAppWidgetProviderTarget.apk" />
<option name="test-file-name" value="CtsAppEnumerationAppWidgetProviderSharedUidTarget.apk" />
<option name="test-file-name" value="CtsAppEnumerationPreferredActivityTarget.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationQueriesNothingReceivesPersistableUri.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationQueriesNothingReceivesNonPersistableUri.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
@@ -96,6 +100,8 @@
<option name="push" value="CtsAppEnumerationStub.apk->/data/local/tmp/cts/appenumeration/CtsAppEnumerationStub.apk" />
<option name="push" value="CtsAppEnumerationFilters.apk->/data/local/tmp/cts/appenumeration/CtsAppEnumerationFilters.apk" />
<option name="push" value="CtsAppEnumerationQueriesNothingSeesInstaller.apk->/data/local/tmp/cts/appenumeration/CtsAppEnumerationQueriesNothingSeesInstaller.apk" />
+ <option name="push" value="CtsAppEnumerationQueriesNothingReceivesPersistableUri.apk->/data/local/tmp/cts/appenumeration/CtsAppEnumerationQueriesNothingReceivesPersistableUri.apk" />
+ <option name="push" value="CtsAppEnumerationQueriesNothingReceivesNonPersistableUri.apk->/data/local/tmp/cts/appenumeration/CtsAppEnumerationQueriesNothingReceivesNonPersistableUri.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/appenumeration/OWNERS b/tests/tests/appenumeration/OWNERS
index 8a44fb2..d1eff73 100644
--- a/tests/tests/appenumeration/OWNERS
+++ b/tests/tests/appenumeration/OWNERS
@@ -2,4 +2,4 @@
patb@google.com
toddke@google.com
chiuwinson@google.com
-rtmitchell@google.com
+zyy@google.com
diff --git a/tests/tests/appenumeration/app/source/Android.bp b/tests/tests/appenumeration/app/source/Android.bp
index deee2f4..2f88d57 100644
--- a/tests/tests/appenumeration/app/source/Android.bp
+++ b/tests/tests/appenumeration/app/source/Android.bp
@@ -18,6 +18,7 @@
java_defaults {
name: "CtsAppEnumerationQueriesDefaults",
+ defaults: ["cts_support_defaults"],
srcs: ["src/**/*.java"],
static_libs: ["CtsAppEnumerationTestLib"],
sdk_version: "test_current",
@@ -57,6 +58,28 @@
}
android_test_helper_app {
+ name: "CtsAppEnumerationQueriesNothingReceivesPersistableUri",
+ manifest: "AndroidManifest-queriesNothing-receivesPersistableUri.xml",
+ defaults: ["CtsAppEnumerationQueriesDefaults"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsAppEnumerationQueriesNothingReceivesNonPersistableUri",
+ manifest: "AndroidManifest-queriesNothing-receivesNonPersistableUri.xml",
+ defaults: ["CtsAppEnumerationQueriesDefaults"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
name: "CtsAppEnumerationQueriesNothingSeesInstaller",
manifest: "AndroidManifest-queriesNothing-seesInstaller.xml",
defaults: ["CtsAppEnumerationQueriesDefaults"],
@@ -167,6 +190,17 @@
}
android_test_helper_app {
+ name: "CtsAppEnumerationQueriesPackageHasProvider",
+ manifest: "AndroidManifest-queriesPackage-hasProvider.xml",
+ defaults: ["CtsAppEnumerationQueriesDefaults"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
name: "CtsAppEnumerationQueriesNothingTargetsQ",
manifest: "AndroidManifest-queriesNothing-targetsQ.xml",
defaults: ["CtsAppEnumerationQueriesDefaults"],
@@ -242,6 +276,7 @@
"general-tests",
],
}
+
android_test_helper_app {
name: "CtsAppEnumerationWildcardBrowsableActivitySource",
manifest: "AndroidManifest-queriesWildcard-browsableActivity.xml",
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml
index 6641c9a..73bd8f3 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml
@@ -26,7 +26,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml
index 7e84769..5a8933e 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml
@@ -22,7 +22,7 @@
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
<provider android:name="android.appenumeration.cts.query.TestProvider"
android:exported="true"
@@ -32,5 +32,9 @@
android:readPermission="android.appenumeration.queries.nothing.haspermission.READ"
android:grantUriPermissions="true"
android:authorities="android.appenumeration.queries.nothing.haspermission2" />
+ <provider android:name="android.appenumeration.cts.query.TestProvider"
+ android:exported="false"
+ android:grantUriPermissions="true"
+ android:authorities="android.appenumeration.queries.nothing.haspermission3" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml
index d046e70..2cf2f6a 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml
@@ -19,7 +19,7 @@
package="android.appenumeration.queries.nothing.hasprovider">
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
<provider android:name="android.appenumeration.cts.query.TestProvider"
android:authorities="android.appenumeration.queries.nothing.hasprovider"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesNonPersistableUri.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesNonPersistableUri.xml
new file mode 100644
index 0000000..9b64bf2
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesNonPersistableUri.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ - Copyright (C) 2021 The Android Open Source Project
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT 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.appenumeration.queries.nothing.receives.nonpersistable.uri">
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesPermissionProtectedUri.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesPermissionProtectedUri.xml
index da644a8..5512d23 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesPermissionProtectedUri.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesPermissionProtectedUri.xml
@@ -19,7 +19,7 @@
package="android.appenumeration.queries.nothing.receives.perm.uri">
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesPersistableUri.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesPersistableUri.xml
new file mode 100644
index 0000000..420056d
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesPersistableUri.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ - Copyright (C) 2021 The Android Open Source Project
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT 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.appenumeration.queries.nothing.receives.persistable.uri">
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesUri.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesUri.xml
index 18ab7ef..8c7ffd7 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesUri.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesUri.xml
@@ -19,7 +19,7 @@
package="android.appenumeration.queries.nothing.receives.uri">
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml
index 1d5f0bc..bfc855f 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml
@@ -19,7 +19,7 @@
package="android.appenumeration.queries.nothing.sees.installer">
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml
index 90db3e6..5f50b62 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml
@@ -20,7 +20,7 @@
android:sharedUserId="android.appenumeration.shareduid">
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml
index 60a0c2d..638d9fa 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml
@@ -20,7 +20,7 @@
<uses-sdk android:targetSdkVersion="29" />
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-usesLibrary.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-usesLibrary.xml
index 99edef0..3a7ef8a 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-usesLibrary.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-usesLibrary.xml
@@ -20,7 +20,7 @@
<application>
<uses-library android:name="android.test.runner" />
<uses-library android:name="com.android.cts.ctsshim.shared_library" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-usesOptionalLibrary.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-usesOptionalLibrary.xml
index 87b7738..65dde1f 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-usesOptionalLibrary.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-usesOptionalLibrary.xml
@@ -21,7 +21,7 @@
<uses-library android:name="android.test.runner" />
<uses-library android:name="com.android.cts.ctsshim.shared_library"
android:required="false" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml
index e5e160a..3bda428 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml
@@ -19,7 +19,7 @@
package="android.appenumeration.queries.nothing">
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage-hasProvider.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage-hasProvider.xml
new file mode 100644
index 0000000..2225d1f
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage-hasProvider.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.appenumeration.queries.pkg.hasprovider">
+
+ <queries>
+ <package android:name="android.appenumeration.noapi" />
+ </queries>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
+ <provider android:name="android.appenumeration.cts.query.TestProvider"
+ android:authorities="android.appenumeration.queries.pkg.hasprovider"
+ android:grantUriPermissions="true"
+ android:exported="false" >
+ </provider>
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml
index 4588f53..956fc04 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml
@@ -24,11 +24,12 @@
<package android:name="android.appenumeration.syncadapter" />
<package android:name="android.appenumeration.appwidgetprovider" />
<package android:name="android.appenumeration.preferred.activity" />
+ <package android:name="com.android.cts.install.lib.testapp.A" />
</queries>
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAction.xml
index 2f1cf69..6d7567f 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAction.xml
@@ -26,7 +26,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml
index 7fb4191..9a17108 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml
@@ -24,7 +24,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml
index dab3e2e..78fd937 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml
@@ -26,7 +26,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml
index aabb703..a930b3c 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml
@@ -26,7 +26,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAction.xml
index 72dfa6b..40732bf 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAction.xml
@@ -26,7 +26,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml
index 09755f0..d92cfbd 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml
@@ -24,7 +24,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml
index d1fdd13..32b6d13 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml
@@ -26,7 +26,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browsableActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browsableActivity.xml
index b08cc12..823dcd0 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browsableActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browsableActivity.xml
@@ -28,7 +28,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browserActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browserActivity.xml
index b6f96aa..6230245 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browserActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browserActivity.xml
@@ -33,7 +33,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-contactsActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-contactsActivity.xml
index 80138c5..c3e754b 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-contactsActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-contactsActivity.xml
@@ -29,7 +29,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-documentEditorActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-documentEditorActivity.xml
index 0f7e971..48191b1 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-documentEditorActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-documentEditorActivity.xml
@@ -28,7 +28,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-shareActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-shareActivity.xml
index e9a051b..ceed8e9 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-shareActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-shareActivity.xml
@@ -27,7 +27,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-webActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-webActivity.xml
index 74412cc..9f0f2d2 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-webActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-webActivity.xml
@@ -33,7 +33,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcardAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcardAction.xml
index 64e3af3..c538cc4 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcardAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcardAction.xml
@@ -24,7 +24,7 @@
</queries>
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.appenumeration.cts.query.TestActivity"
+ <activity android:name="android.appenumeration.cts.TestActivity"
android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java b/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
deleted file mode 100644
index bcbbc53..0000000
--- a/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
+++ /dev/null
@@ -1,685 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.appenumeration.cts.query;
-
-import static android.appenumeration.cts.Constants.ACTION_CHECK_SIGNATURES;
-import static android.appenumeration.cts.Constants.ACTION_GET_INSTALLED_PACKAGES;
-import static android.appenumeration.cts.Constants.ACTION_GET_NAMES_FOR_UIDS;
-import static android.appenumeration.cts.Constants.ACTION_GET_NAME_FOR_UID;
-import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGES_FOR_UID;
-import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGE_INFO;
-import static android.appenumeration.cts.Constants.ACTION_HAS_SIGNING_CERTIFICATE;
-import static android.appenumeration.cts.Constants.ACTION_JUST_FINISH;
-import static android.appenumeration.cts.Constants.ACTION_QUERY_ACTIVITIES;
-import static android.appenumeration.cts.Constants.ACTION_QUERY_PROVIDERS;
-import static android.appenumeration.cts.Constants.ACTION_QUERY_SERVICES;
-import static android.appenumeration.cts.Constants.ACTION_SEND_RESULT;
-import static android.appenumeration.cts.Constants.ACTION_START_DIRECTLY;
-import static android.appenumeration.cts.Constants.ACTION_START_FOR_RESULT;
-import static android.appenumeration.cts.Constants.ACTION_START_SENDER_FOR_RESULT;
-import static android.appenumeration.cts.Constants.CALLBACK_EVENT_INVALID;
-import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGES_AVAILABLE;
-import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGES_SUSPENDED;
-import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGES_UNAVAILABLE;
-import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGES_UNSUSPENDED;
-import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_ADDED;
-import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_CHANGED;
-import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_REMOVED;
-import static android.appenumeration.cts.Constants.EXTRA_AUTHORITY;
-import static android.appenumeration.cts.Constants.EXTRA_CERT;
-import static android.appenumeration.cts.Constants.EXTRA_DATA;
-import static android.appenumeration.cts.Constants.EXTRA_ERROR;
-import static android.appenumeration.cts.Constants.EXTRA_FLAGS;
-import static android.appenumeration.cts.Constants.EXTRA_REMOTE_CALLBACK;
-import static android.appenumeration.cts.Constants.EXTRA_REMOTE_READY_CALLBACK;
-import static android.content.Intent.EXTRA_COMPONENT_NAME;
-import static android.content.Intent.EXTRA_PACKAGES;
-import static android.content.Intent.EXTRA_RETURN_RESULT;
-import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
-import static android.os.Process.INVALID_UID;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.appenumeration.cts.Constants;
-import android.appenumeration.cts.MissingBroadcastException;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.content.ServiceConnection;
-import android.content.SyncAdapterType;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.SharedLibraryInfo;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Parcelable;
-import android.os.PatternMatcher;
-import android.os.Process;
-import android.os.RemoteCallback;
-import android.os.UserHandle;
-import android.util.SparseArray;
-import android.view.accessibility.AccessibilityManager;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public class TestActivity extends Activity {
-
- private final static long TIMEOUT_MS = 3000;
-
- /**
- * Extending the timeout time of non broadcast receivers, avoid not
- * receiving callbacks in time on some common low-end platforms and
- * do not affect the situation that callback can be received in advance.
- */
- private final static long EXTENDED_TIMEOUT_MS = 5000;
-
- SparseArray<RemoteCallback> callbacks = new SparseArray<>();
-
- private Handler mainHandler;
- private Handler backgroundHandler;
- private HandlerThread backgroundThread;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- mainHandler = new Handler(getMainLooper());
- backgroundThread = new HandlerThread("testBackground");
- backgroundThread.start();
- backgroundHandler = new Handler(backgroundThread.getLooper());
- super.onCreate(savedInstanceState);
- handleIntent(getIntent());
- onCommandReady(getIntent());
- }
-
- @Override
- protected void onDestroy() {
- backgroundThread.quitSafely();
- super.onDestroy();
- }
-
- private void handleIntent(Intent intent) {
- RemoteCallback remoteCallback = intent.getParcelableExtra(EXTRA_REMOTE_CALLBACK);
- try {
- final String action = intent.getAction();
- final Intent queryIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT);
- if (ACTION_GET_PACKAGE_INFO.equals(action)) {
- final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
- sendPackageInfo(remoteCallback, packageName);
- } else if (ACTION_GET_PACKAGES_FOR_UID.equals(action)) {
- final int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
- sendPackagesForUid(remoteCallback, uid);
- } else if (ACTION_GET_NAME_FOR_UID.equals(action)) {
- final int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
- sendNameForUid(remoteCallback, uid);
- } else if (ACTION_GET_NAMES_FOR_UIDS.equals(action)) {
- final int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
- sendNamesForUids(remoteCallback, uid);
- } else if (ACTION_CHECK_SIGNATURES.equals(action)) {
- final int uid1 = getPackageManager().getApplicationInfo(
- getPackageName(), /* flags */ 0).uid;
- final int uid2 = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
- sendCheckSignatures(remoteCallback, uid1, uid2);
- } else if (ACTION_HAS_SIGNING_CERTIFICATE.equals(action)) {
- final int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
- final byte[] cert = intent.getBundleExtra(EXTRA_DATA).getByteArray(EXTRA_CERT);
- sendHasSigningCertificate(remoteCallback, uid, cert, CERT_INPUT_RAW_X509);
- } else if (ACTION_START_FOR_RESULT.equals(action)) {
- final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
- int requestCode = RESULT_FIRST_USER + callbacks.size();
- callbacks.put(requestCode, remoteCallback);
- startActivityForResult(
- new Intent(ACTION_SEND_RESULT).setComponent(
- new ComponentName(packageName, getClass().getCanonicalName())),
- requestCode);
- // don't send anything... await result callback
- } else if (ACTION_SEND_RESULT.equals(action)) {
- try {
- setResult(RESULT_OK,
- getIntent().putExtra(
- Intent.EXTRA_RETURN_RESULT,
- getPackageManager().getPackageInfo(getCallingPackage(), 0)));
- } catch (PackageManager.NameNotFoundException e) {
- setResult(RESULT_FIRST_USER, new Intent().putExtra("error", e));
- }
- finish();
- } else if (ACTION_QUERY_ACTIVITIES.equals(action)) {
- sendQueryIntentActivities(remoteCallback, queryIntent);
- } else if (ACTION_QUERY_SERVICES.equals(action)) {
- sendQueryIntentServices(remoteCallback, queryIntent);
- } else if (ACTION_QUERY_PROVIDERS.equals(action)) {
- sendQueryIntentProviders(remoteCallback, queryIntent);
- } else if (ACTION_START_DIRECTLY.equals(action)) {
- try {
- startActivity(queryIntent);
- remoteCallback.sendResult(new Bundle());
- } catch (ActivityNotFoundException e) {
- sendError(remoteCallback, e);
- }
- finish();
- } else if (ACTION_JUST_FINISH.equals(action)) {
- finish();
- } else if (ACTION_GET_INSTALLED_PACKAGES.equals(action)) {
- sendGetInstalledPackages(remoteCallback, queryIntent.getIntExtra(EXTRA_FLAGS, 0));
- } else if (ACTION_START_SENDER_FOR_RESULT.equals(action)) {
- PendingIntent pendingIntent = intent.getParcelableExtra("pendingIntent");
- int requestCode = RESULT_FIRST_USER + callbacks.size();
- callbacks.put(requestCode, remoteCallback);
- try {
- startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, null,
- 0, 0, 0);
- } catch (IntentSender.SendIntentException e) {
- sendError(remoteCallback, e);
- }
- } else if (Constants.ACTION_AWAIT_PACKAGE_REMOVED.equals(action)) {
- final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
- awaitPackageBroadcast(
- remoteCallback, packageName, Intent.ACTION_PACKAGE_REMOVED, TIMEOUT_MS);
- } else if (Constants.ACTION_AWAIT_PACKAGE_ADDED.equals(action)) {
- final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
- awaitPackageBroadcast(
- remoteCallback, packageName, Intent.ACTION_PACKAGE_ADDED, TIMEOUT_MS);
- } else if (Constants.ACTION_QUERY_RESOLVER.equals(action)) {
- final String authority = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
- queryResolverForVisiblePackages(remoteCallback, authority);
- } else if (Constants.ACTION_BIND_SERVICE.equals(action)) {
- final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
- bindService(remoteCallback, packageName);
- } else if (Constants.ACTION_GET_SYNCADAPTER_TYPES.equals(action)) {
- sendSyncAdapterTypes(remoteCallback);
- } else if (Constants.ACTION_GET_INSTALLED_APPWIDGET_PROVIDERS.equals(action)) {
- sendInstalledAppWidgetProviders(remoteCallback);
- } else if (Constants.ACTION_AWAIT_PACKAGES_SUSPENDED.equals(action)) {
- final String[] awaitPackages = intent.getBundleExtra(EXTRA_DATA)
- .getStringArray(EXTRA_PACKAGES);
- awaitSuspendedPackagesBroadcast(remoteCallback, Arrays.asList(awaitPackages),
- Intent.ACTION_PACKAGES_SUSPENDED, TIMEOUT_MS);
- } else if (Constants.ACTION_LAUNCHER_APPS_IS_ACTIVITY_ENABLED.equals(action)) {
- final String componentName = intent.getBundleExtra(EXTRA_DATA)
- .getString(EXTRA_COMPONENT_NAME);
- sendIsActivityEnabled(remoteCallback, ComponentName.unflattenFromString(
- componentName));
- } else if (Constants.ACTION_GET_SYNCADAPTER_PACKAGES_FOR_AUTHORITY.equals(action)) {
- final String authority = intent.getBundleExtra(EXTRA_DATA)
- .getString(EXTRA_AUTHORITY);
- final int userId = intent.getBundleExtra(EXTRA_DATA)
- .getInt(Intent.EXTRA_USER);
- sendSyncAdapterPackagesForAuthorityAsUser(remoteCallback, authority, userId);
- } else if (Constants.ACTION_AWAIT_LAUNCHER_APPS_CALLBACK.equals(action)) {
- final int expectedEventCode = intent.getBundleExtra(EXTRA_DATA)
- .getInt(EXTRA_FLAGS, CALLBACK_EVENT_INVALID);
- awaitLauncherAppsCallback(remoteCallback, expectedEventCode, EXTENDED_TIMEOUT_MS);
- } else if (Constants.ACTION_GET_SHAREDLIBRARY_DEPENDENT_PACKAGES.equals(action)) {
- final String sharedLibName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
- sendGetSharedLibraryDependentPackages(remoteCallback, sharedLibName);
- } else if (Constants.ACTION_GET_PREFERRED_ACTIVITIES.equals(action)) {
- sendGetPreferredActivities(remoteCallback);
- } else if (Constants.ACTION_SET_INSTALLER_PACKAGE_NAME.equals(action)) {
- final String targetPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
- final String installerPackageName = intent.getBundleExtra(EXTRA_DATA)
- .getString(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
- sendSetInstallerPackageName(remoteCallback, targetPackageName,
- installerPackageName);
- } else if (Constants.ACTION_GET_INSTALLED_ACCESSIBILITYSERVICES_PACKAGES.equals(
- action)) {
- sendGetInstalledAccessibilityServicePackages(remoteCallback);
- } else {
- sendError(remoteCallback, new Exception("unknown action " + action));
- }
- } catch (Exception e) {
- sendError(remoteCallback, e);
- }
- }
-
- private void sendGetInstalledAccessibilityServicePackages(RemoteCallback remoteCallback) {
- final String[] packages = getSystemService(
- AccessibilityManager.class).getInstalledAccessibilityServiceList().stream().map(
- p -> p.getComponentName().getPackageName()).distinct().toArray(String[]::new);
- final Bundle result = new Bundle();
- result.putStringArray(EXTRA_RETURN_RESULT, packages);
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void onCommandReady(Intent intent) {
- final RemoteCallback callback = intent.getParcelableExtra(EXTRA_REMOTE_READY_CALLBACK);
- if (callback != null) {
- callback.sendResult(null);
- }
- }
-
- private void awaitPackageBroadcast(RemoteCallback remoteCallback, String packageName,
- String action, long timeoutMs) {
- final IntentFilter filter = new IntentFilter(action);
- filter.addDataScheme("package");
- filter.addDataSchemeSpecificPart(packageName, PatternMatcher.PATTERN_LITERAL);
- final Object token = new Object();
- registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final Bundle result = new Bundle();
- result.putString(EXTRA_DATA, intent.getDataString());
- remoteCallback.sendResult(result);
- mainHandler.removeCallbacksAndMessages(token);
- finish();
- }
- }, filter);
- mainHandler.postDelayed(
- () -> sendError(remoteCallback,
- new MissingBroadcastException(action, timeoutMs)),
- token, timeoutMs);
- }
-
- private void awaitSuspendedPackagesBroadcast(RemoteCallback remoteCallback,
- List<String> awaitList, String action, long timeoutMs) {
- final IntentFilter filter = new IntentFilter(action);
- final ArrayList<String> suspendedList = new ArrayList<>();
- final Object token = new Object();
- final Runnable sendResult = () -> {
- final Bundle result = new Bundle();
- result.putStringArray(EXTRA_PACKAGES, suspendedList.toArray(new String[] {}));
- remoteCallback.sendResult(result);
- finish();
- };
- registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final Bundle extras = intent.getExtras();
- final String[] changedList = extras.getStringArray(
- Intent.EXTRA_CHANGED_PACKAGE_LIST);
- suspendedList.addAll(Arrays.stream(changedList).filter(
- p -> awaitList.contains(p)).collect(Collectors.toList()));
- if (suspendedList.size() == awaitList.size()) {
- mainHandler.removeCallbacksAndMessages(token);
- sendResult.run();
- }
- }
- }, filter);
- mainHandler.postDelayed(() -> sendResult.run(), token, timeoutMs);
- }
-
- private void awaitLauncherAppsCallback(RemoteCallback remoteCallback, int expectedEventCode,
- long timeoutMs) {
- final Object token = new Object();
- final Bundle result = new Bundle();
- final LauncherApps launcherApps = getSystemService(LauncherApps.class);
- final LauncherApps.Callback launcherAppsCallback = new LauncherApps.Callback() {
-
- private void onPackageStateUpdated(String[] packageNames, int resultCode) {
- if (resultCode != expectedEventCode) {
- return;
- }
-
- mainHandler.removeCallbacksAndMessages(token);
- result.putStringArray(EXTRA_PACKAGES, packageNames);
- result.putInt(EXTRA_FLAGS, resultCode);
- remoteCallback.sendResult(result);
-
- launcherApps.unregisterCallback(this);
- finish();
- }
-
- @Override
- public void onPackageRemoved(String packageName, UserHandle user) {
- onPackageStateUpdated(new String[]{packageName}, CALLBACK_EVENT_PACKAGE_REMOVED);
- }
-
- @Override
- public void onPackageAdded(String packageName, UserHandle user) {
- onPackageStateUpdated(new String[]{packageName}, CALLBACK_EVENT_PACKAGE_ADDED);
- }
-
- @Override
- public void onPackageChanged(String packageName, UserHandle user) {
- onPackageStateUpdated(new String[]{packageName}, CALLBACK_EVENT_PACKAGE_CHANGED);
- }
-
- @Override
- public void onPackagesAvailable(String[] packageNames, UserHandle user,
- boolean replacing) {
- onPackageStateUpdated(packageNames, CALLBACK_EVENT_PACKAGES_AVAILABLE);
- }
-
- @Override
- public void onPackagesUnavailable(String[] packageNames, UserHandle user,
- boolean replacing) {
- onPackageStateUpdated(packageNames, CALLBACK_EVENT_PACKAGES_UNAVAILABLE);
- }
-
- @Override
- public void onPackagesSuspended(String[] packageNames, UserHandle user) {
- onPackageStateUpdated(packageNames, CALLBACK_EVENT_PACKAGES_SUSPENDED);
- super.onPackagesSuspended(packageNames, user);
- }
-
- @Override
- public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
- onPackageStateUpdated(packageNames, CALLBACK_EVENT_PACKAGES_UNSUSPENDED);
- super.onPackagesUnsuspended(packageNames, user);
- }
- };
-
- launcherApps.registerCallback(launcherAppsCallback);
-
- mainHandler.postDelayed(() -> {
- result.putStringArray(EXTRA_PACKAGES, new String[]{});
- result.putInt(EXTRA_FLAGS, CALLBACK_EVENT_INVALID);
- remoteCallback.sendResult(result);
-
- launcherApps.unregisterCallback(launcherAppsCallback);
- finish();
- }, token, timeoutMs);
- }
-
- private void sendGetInstalledPackages(RemoteCallback remoteCallback, int flags) {
- String[] packages =
- getPackageManager().getInstalledPackages(flags)
- .stream().map(p -> p.packageName).distinct().toArray(String[]::new);
- Bundle result = new Bundle();
- result.putStringArray(EXTRA_RETURN_RESULT, packages);
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void sendQueryIntentActivities(RemoteCallback remoteCallback, Intent queryIntent) {
- final String[] resolveInfos = getPackageManager().queryIntentActivities(
- queryIntent, 0 /* flags */).stream()
- .map(ri -> ri.activityInfo.applicationInfo.packageName)
- .distinct()
- .toArray(String[]::new);
- Bundle result = new Bundle();
- result.putStringArray(EXTRA_RETURN_RESULT, resolveInfos);
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void sendQueryIntentServices(RemoteCallback remoteCallback, Intent queryIntent) {
- final String[] resolveInfos = getPackageManager().queryIntentServices(
- queryIntent, 0 /* flags */).stream()
- .map(ri -> ri.serviceInfo.applicationInfo.packageName)
- .distinct()
- .toArray(String[]::new);
- Bundle result = new Bundle();
- result.putStringArray(EXTRA_RETURN_RESULT, resolveInfos);
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void sendQueryIntentProviders(RemoteCallback remoteCallback, Intent queryIntent) {
- final String[] resolveInfos = getPackageManager().queryIntentContentProviders(
- queryIntent, 0 /* flags */).stream()
- .map(ri -> ri.providerInfo.applicationInfo.packageName)
- .distinct()
- .toArray(String[]::new);
- Bundle result = new Bundle();
- result.putStringArray(EXTRA_RETURN_RESULT, resolveInfos);
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void queryResolverForVisiblePackages(RemoteCallback remoteCallback, String authority) {
- backgroundHandler.post(() -> {
- Uri queryUri = Uri.parse("content://" + authority + "/test");
- Cursor query = getContentResolver().query(queryUri, null, null, null, null);
- if (query == null || !query.moveToFirst()) {
- sendError(remoteCallback,
- new IllegalStateException(
- "Query of " + queryUri + " could not be completed"));
- return;
- }
- ArrayList<String> visiblePackages = new ArrayList<>();
- while (!query.isAfterLast()) {
- visiblePackages.add(query.getString(0));
- query.moveToNext();
- }
- query.close();
-
- mainHandler.post(() -> {
- Bundle result = new Bundle();
- result.putStringArray(EXTRA_RETURN_RESULT, visiblePackages.toArray(new String[]{}));
- remoteCallback.sendResult(result);
- finish();
- });
-
- });
- }
-
- private void sendError(RemoteCallback remoteCallback, Exception failure) {
- Bundle result = new Bundle();
- result.putSerializable(EXTRA_ERROR, failure);
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void sendPackageInfo(RemoteCallback remoteCallback, String packageName) {
- final PackageInfo pi;
- try {
- pi = getPackageManager().getPackageInfo(packageName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- sendError(remoteCallback, e);
- return;
- }
- Bundle result = new Bundle();
- result.putParcelable(EXTRA_RETURN_RESULT, pi);
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void sendPackagesForUid(RemoteCallback remoteCallback, int uid) {
- final String[] packages = getPackageManager().getPackagesForUid(uid);
- final Bundle result = new Bundle();
- result.putStringArray(EXTRA_RETURN_RESULT, packages);
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void sendNameForUid(RemoteCallback remoteCallback, int uid) {
- final String name = getPackageManager().getNameForUid(uid);
- final Bundle result = new Bundle();
- result.putString(EXTRA_RETURN_RESULT, name);
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void sendNamesForUids(RemoteCallback remoteCallback, int uid) {
- final String[] names = getPackageManager().getNamesForUids(new int[]{uid});
- final Bundle result = new Bundle();
- result.putStringArray(EXTRA_RETURN_RESULT, names);
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void sendCheckSignatures(RemoteCallback remoteCallback, int uid1, int uid2) {
- final int signatureResult = getPackageManager().checkSignatures(uid1, uid2);
- final Bundle result = new Bundle();
- result.putInt(EXTRA_RETURN_RESULT, signatureResult);
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void sendHasSigningCertificate(RemoteCallback remoteCallback, int uid, byte[] cert,
- int type) {
- final boolean signatureResult = getPackageManager().hasSigningCertificate(uid, cert, type);
- final Bundle result = new Bundle();
- result.putBoolean(EXTRA_RETURN_RESULT, signatureResult);
- remoteCallback.sendResult(result);
- finish();
- }
-
- /**
- * Instead of sending a list of package names, this function sends a List of
- * {@link SyncAdapterType}, since the {@link SyncAdapterType#getPackageName()} is a test api
- * which can only be invoked in the instrumentation.
- */
- private void sendSyncAdapterTypes(RemoteCallback remoteCallback) {
- final SyncAdapterType[] types = ContentResolver.getSyncAdapterTypes();
- final ArrayList<Parcelable> parcelables = new ArrayList<>();
- for (SyncAdapterType type : types) {
- parcelables.add(type);
- }
- final Bundle result = new Bundle();
- result.putParcelableArrayList(EXTRA_RETURN_RESULT, parcelables);
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void sendIsActivityEnabled(RemoteCallback remoteCallback, ComponentName componentName) {
- final LauncherApps launcherApps = getSystemService(LauncherApps.class);
- final Bundle result = new Bundle();
- try {
- result.putBoolean(EXTRA_RETURN_RESULT, launcherApps.isActivityEnabled(componentName,
- Process.myUserHandle()));
- } catch (IllegalArgumentException e) {
- }
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void sendInstalledAppWidgetProviders(RemoteCallback remoteCallback) {
- final AppWidgetManager appWidgetManager = getSystemService(AppWidgetManager.class);
- final List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
- final ArrayList<Parcelable> parcelables = new ArrayList<>();
- for (AppWidgetProviderInfo info : providers) {
- parcelables.add(info);
- }
- final Bundle result = new Bundle();
- result.putParcelableArrayList(EXTRA_RETURN_RESULT, parcelables);
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void sendSyncAdapterPackagesForAuthorityAsUser(RemoteCallback remoteCallback,
- String authority, int userId) {
- final String[] syncAdapterPackages = ContentResolver
- .getSyncAdapterPackagesForAuthorityAsUser(authority, userId);
- final Bundle result = new Bundle();
- result.putStringArray(Intent.EXTRA_PACKAGES, syncAdapterPackages);
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void sendGetSharedLibraryDependentPackages(RemoteCallback remoteCallback,
- String sharedLibName) {
- final List<SharedLibraryInfo> sharedLibraryInfos = getPackageManager()
- .getSharedLibraries(0 /* flags */);
- SharedLibraryInfo sharedLibraryInfo = sharedLibraryInfos.stream().filter(
- info -> sharedLibName.equals(info.getName())).findAny().orElse(null);
- final String[] dependentPackages = sharedLibraryInfo == null ? null
- : sharedLibraryInfo.getDependentPackages().stream()
- .map(versionedPackage -> versionedPackage.getPackageName())
- .distinct().collect(Collectors.toList()).toArray(new String[]{});
- final Bundle result = new Bundle();
- result.putStringArray(Intent.EXTRA_PACKAGES, dependentPackages);
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void sendGetPreferredActivities(RemoteCallback remoteCallback) {
- final List<IntentFilter> filters = new ArrayList<>();
- final List<ComponentName> activities = new ArrayList<>();
- getPackageManager().getPreferredActivities(filters, activities, null /* packageName*/);
- final String[] packages = activities.stream()
- .map(componentName -> componentName.getPackageName()).distinct()
- .collect(Collectors.toList()).toArray(new String[]{});
- final Bundle result = new Bundle();
- result.putStringArray(Intent.EXTRA_PACKAGES, packages);
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void sendSetInstallerPackageName(RemoteCallback remoteCallback,
- String targetPackageName, String installerPackageName) {
- try {
- getPackageManager().setInstallerPackageName(targetPackageName, installerPackageName);
- remoteCallback.sendResult(null);
- finish();
- } catch (Exception e) {
- sendError(remoteCallback, e);
- }
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- final RemoteCallback remoteCallback = callbacks.get(requestCode);
- if (resultCode != RESULT_OK) {
- Exception e = (Exception) data.getSerializableExtra(EXTRA_ERROR);
- sendError(remoteCallback, e == null ? new Exception("Result was " + resultCode) : e);
- return;
- }
- final Bundle result = new Bundle();
- result.putParcelable(EXTRA_RETURN_RESULT, data.getParcelableExtra(EXTRA_RETURN_RESULT));
- remoteCallback.sendResult(result);
- finish();
- }
-
- private void bindService(RemoteCallback remoteCallback, String packageName) {
- final String SERVICE_NAME = "android.appenumeration.testapp.DummyService";
- final Intent intent = new Intent();
- intent.setClassName(packageName, SERVICE_NAME);
- final ServiceConnection serviceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- // No-op
- }
-
- @Override
- public void onServiceDisconnected(ComponentName className) {
- // No-op
- }
-
- @Override
- public void onBindingDied(ComponentName name) {
- // Remote service die
- finish();
- }
-
- @Override
- public void onNullBinding(ComponentName name) {
- // Since the DummyService doesn't implement onBind, it returns null and
- // onNullBinding would be called. Use postDelayed to keep this service
- // connection alive for 3 seconds.
- mainHandler.postDelayed(() -> {
- unbindService(this);
- finish();
- }, TIMEOUT_MS);
- }
- };
-
- final boolean bound = bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
- final Bundle result = new Bundle();
- result.putBoolean(EXTRA_RETURN_RESULT, bound);
- remoteCallback.sendResult(result);
- // Don't invoke finish() right here if service is bound successfully to keep the service
- // connection alive since the ServiceRecord would be remove from the ServiceMap once no
- // client is binding the service.
- if (!bound) finish();
- }
-}
diff --git a/tests/tests/appenumeration/app/target/Android.bp b/tests/tests/appenumeration/app/target/Android.bp
index d3d8bd2..8b81354 100644
--- a/tests/tests/appenumeration/app/target/Android.bp
+++ b/tests/tests/appenumeration/app/target/Android.bp
@@ -16,228 +16,209 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+java_defaults {
+ name: "CtsAppEnumerationTargetDefaults",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+ static_libs: ["CtsAppEnumerationTestLib"],
+ sdk_version: "test_current",
+}
+
android_test_helper_app {
name: "CtsAppEnumerationForceQueryable",
manifest: "AndroidManifest-forceQueryable.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
}
android_test_helper_app {
name: "CtsAppEnumerationForceQueryableNormalInstall",
manifest: "AndroidManifest-forceQueryable-normalInstall.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
}
android_test_helper_app {
name: "CtsAppEnumerationFilters",
manifest: "AndroidManifest-filters.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
}
android_test_helper_app {
name: "CtsAppEnumerationNoApi",
manifest: "AndroidManifest-noapi.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
}
android_test_helper_app {
name: "CtsAppEnumerationStub",
manifest: "AndroidManifest-stub.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
}
android_test_helper_app {
name: "CtsAppEnumerationSharedUidTarget",
manifest: "AndroidManifest-noapi-sharedUser.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
}
android_test_helper_app {
name: "CtsAppEnumerationContactsActivityTarget",
manifest: "AndroidManifest-contactsActivity.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
}
android_test_helper_app {
name: "CtsAppEnumerationDocumentsActivityTarget",
manifest: "AndroidManifest-documentEditorActivity.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
}
android_test_helper_app {
name: "CtsAppEnumerationShareActivityTarget",
manifest: "AndroidManifest-shareActivity.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
}
android_test_helper_app {
name: "CtsAppEnumerationWebActivityTarget",
manifest: "AndroidManifest-webActivity.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
+}
+
+android_test_helper_app {
+ name: "CtsAppEnumerationPrefixWildcardWebActivityTarget",
+ manifest: "AndroidManifest-prefixWildcardWebActivity.xml",
+ defaults: ["CtsAppEnumerationTargetDefaults"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
}
android_test_helper_app {
name: "CtsAppEnumerationBrowserActivityTarget",
manifest: "AndroidManifest-browserActivity.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
}
android_test_helper_app {
name: "CtsAppEnumerationBrowserWildcardActivityTarget",
manifest: "AndroidManifest-browserWildcardActivity.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
}
android_test_helper_app {
name: "CtsAppEnumerationSyncadapterTarget",
manifest: "AndroidManifest-syncadapter.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
- resource_dirs: ["res"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
}
android_test_helper_app {
name: "CtsAppEnumerationSyncadapterSharedUidTarget",
manifest: "AndroidManifest-syncadapter-sharedUser.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
- resource_dirs: ["res"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
}
android_test_helper_app {
name: "CtsAppEnumerationAppWidgetProviderTarget",
manifest: "AndroidManifest-appWidgetProvider.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
- resource_dirs: ["res"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
}
android_test_helper_app {
name: "CtsAppEnumerationAppWidgetProviderSharedUidTarget",
manifest: "AndroidManifest-appWidgetProvider-sharedUser.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
- resource_dirs: ["res"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
}
android_test_helper_app {
name: "CtsAppEnumerationPreferredActivityTarget",
manifest: "AndroidManifest-preferredActivity.xml",
- defaults: ["cts_support_defaults"],
- srcs: ["src/**/*.java"],
- resource_dirs: ["res"],
+ defaults: ["CtsAppEnumerationTargetDefaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
- sdk_version: "test_current",
-}
\ No newline at end of file
+}
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-appWidgetProvider-sharedUser.xml b/tests/tests/appenumeration/app/target/AndroidManifest-appWidgetProvider-sharedUser.xml
index 153894c..0f91979 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-appWidgetProvider-sharedUser.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-appWidgetProvider-sharedUser.xml
@@ -20,6 +20,8 @@
android:sharedUserId="android.appenumeration.shareduid">
<application>
<uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
<receiver android:name="android.appenumeration.MockAppWidgetProvider"
android:exported="true">
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-appWidgetProvider.xml b/tests/tests/appenumeration/app/target/AndroidManifest-appWidgetProvider.xml
index 836acda..100f821 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-appWidgetProvider.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-appWidgetProvider.xml
@@ -19,6 +19,8 @@
package="android.appenumeration.appwidgetprovider">
<application>
<uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
<receiver android:name="android.appenumeration.MockAppWidgetProvider"
android:exported="true">
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-browserActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-browserActivity.xml
index f4aecba..8a0cf23 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-browserActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-browserActivity.xml
@@ -19,6 +19,8 @@
package="android.appenumeration.browser.activity">
<application>
<uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
<activity android:name="android.appenumeration.WebBrowser"
android:exported="true">
<intent-filter>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-browserWildcardActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-browserWildcardActivity.xml
index 97b0d9b..c165ba8 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-browserWildcardActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-browserWildcardActivity.xml
@@ -19,6 +19,8 @@
package="android.appenumeration.browser.wildcard.activity">
<application>
<uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
<activity android:name="android.appenumeration.WebBrowser"
android:exported="true">
<intent-filter>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-contactsActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-contactsActivity.xml
index 0bff9cc..fdd9081 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-contactsActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-contactsActivity.xml
@@ -19,6 +19,8 @@
package="android.appenumeration.contacts.activity">
<application>
<uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
<activity android:name="android.appenumeration.ContactsActivity"
android:exported="true">
<intent-filter>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-documentEditorActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-documentEditorActivity.xml
index 6795cf7..011f59b 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-documentEditorActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-documentEditorActivity.xml
@@ -19,6 +19,8 @@
package="android.appenumeration.editor.activity">
<application>
<uses-library android:name="android.test.runner"/>
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
<activity android:name="android.appenumeration.EditorActivity"
android:exported="true">
<intent-filter>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml b/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
index eeb3170..9f89655 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
@@ -19,6 +19,8 @@
package="android.appenumeration.filters">
<application>
<uses-library android:name="android.test.runner"/>
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
<activity android:name="android.appenumeration.testapp.DummyActivity"
android:visibleToInstantApps="true"
android:exported="true">
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable-normalInstall.xml b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable-normalInstall.xml
index 2918e37..178b6fa 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable-normalInstall.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable-normalInstall.xml
@@ -21,5 +21,7 @@
<!-- This app will not be a system app and should be installed as a normal app (not
forceQueryable) to ensure it's not visible by default -->
<uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
index 3778b04..e835b52 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
@@ -21,5 +21,7 @@
<!-- This app will not be a system app and so must be installed as forceQueryable by the
test framework -->
<uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml b/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml
index 3b5be22..6630f68 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml
@@ -20,5 +20,7 @@
android:sharedUserId="android.appenumeration.shareduid">
<application>
<uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml b/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml
index fc2835e..ced7705 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml
@@ -19,5 +19,7 @@
package="android.appenumeration.noapi">
<application>
<uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-preferredActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-preferredActivity.xml
index fb9e796..8a86825 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-preferredActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-preferredActivity.xml
@@ -19,6 +19,8 @@
package="android.appenumeration.preferred.activity">
<application>
<uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
<activity android:name="android.appenumeration.testapp.DummyActivity"
android:exported="true">
<intent-filter>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-prefixWildcardWebActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-prefixWildcardWebActivity.xml
new file mode 100644
index 0000000..f5bd1aa
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-prefixWildcardWebActivity.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.appenumeration.prefix.wildcard.web.activity">
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
+ <activity android:name="android.appenumeration.WebActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="http"
+ android:host="*.appenumeration.android"/>
+ <data android:scheme="https"
+ android:host="*.appenumeration.android"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-shareActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-shareActivity.xml
index 148fa29..af9e57b 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-shareActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-shareActivity.xml
@@ -19,6 +19,8 @@
package="android.appenumeration.share.activity">
<application>
<uses-library android:name="android.test.runner"/>
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
<activity android:name="android.appenumeration.ShareActivity"
android:exported="true">
<intent-filter>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-stub.xml b/tests/tests/appenumeration/app/target/AndroidManifest-stub.xml
index af8daa4..630358e 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-stub.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-stub.xml
@@ -19,5 +19,7 @@
package="android.appenumeration.stub">
<application>
<uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-syncadapter-sharedUser.xml b/tests/tests/appenumeration/app/target/AndroidManifest-syncadapter-sharedUser.xml
index 158067d..8c8dd09 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-syncadapter-sharedUser.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-syncadapter-sharedUser.xml
@@ -20,8 +20,18 @@
android:sharedUserId="android.appenumeration.shareduid">
<application>
<uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
- <service android:name="android.appenumeration.MockSyncAdapterService"
+ <service android:name="android.appenumeration.testapp.MockAuthenticatorService"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.accounts.AccountAuthenticator" />
+ </intent-filter>
+ <meta-data android:name="android.accounts.AccountAuthenticator"
+ android:resource="@xml/authenticator_shareduser" />
+ </service>
+ <service android:name="android.appenumeration.testapp.MockSyncAdapterService"
android:exported="true">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>
@@ -29,5 +39,9 @@
<meta-data android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter_shareduser"/>
</service>
+
+ <provider android:name="android.appenumeration.testapp.DummyProvider"
+ android:authorities="android.appenumeration.syncadapter.shareduid.authority"
+ android:exported="false" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-syncadapter.xml b/tests/tests/appenumeration/app/target/AndroidManifest-syncadapter.xml
index f1177df..4529f10 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-syncadapter.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-syncadapter.xml
@@ -19,8 +19,18 @@
package="android.appenumeration.syncadapter">
<application>
<uses-library android:name="android.test.runner" />
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
- <service android:name="android.appenumeration.MockSyncAdapterService"
+ <service android:name="android.appenumeration.testapp.MockAuthenticatorService"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.accounts.AccountAuthenticator" />
+ </intent-filter>
+ <meta-data android:name="android.accounts.AccountAuthenticator"
+ android:resource="@xml/authenticator" />
+ </service>
+ <service android:name="android.appenumeration.testapp.MockSyncAdapterService"
android:exported="true">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>
@@ -28,5 +38,9 @@
<meta-data android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter"/>
</service>
+
+ <provider android:name="android.appenumeration.testapp.DummyProvider"
+ android:authorities="android.appenumeration.syncadapter.authority"
+ android:exported="false" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-webActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-webActivity.xml
index 31fe275..bbb5275 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-webActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-webActivity.xml
@@ -19,6 +19,8 @@
package="android.appenumeration.web.activity">
<application>
<uses-library android:name="android.test.runner"/>
+ <activity android:name="android.appenumeration.cts.TestActivity"
+ android:exported="true" />
<activity android:name="android.appenumeration.WebActivity"
android:exported="true">
<intent-filter>
diff --git a/tests/tests/appenumeration/app/target/res/xml/authenticator.xml b/tests/tests/appenumeration/app/target/res/xml/authenticator.xml
new file mode 100644
index 0000000..d3853b7
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/res/xml/authenticator.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accountType="android.appenumeration.account.type"
+ android:label="Mock Account" />
diff --git a/tests/tests/appenumeration/app/target/res/xml/authenticator_shareduser.xml b/tests/tests/appenumeration/app/target/res/xml/authenticator_shareduser.xml
new file mode 100644
index 0000000..8c7936c
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/res/xml/authenticator_shareduser.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accountType="android.appenumeration.shareduid.account.type"
+ android:label="Mock Shared User Account" />
diff --git a/tests/tests/appenumeration/app/target/res/xml/syncadapter_shareduser.xml b/tests/tests/appenumeration/app/target/res/xml/syncadapter_shareduser.xml
index fd47011..2ca7cbe 100644
--- a/tests/tests/appenumeration/app/target/res/xml/syncadapter_shareduser.xml
+++ b/tests/tests/appenumeration/app/target/res/xml/syncadapter_shareduser.xml
@@ -15,5 +15,5 @@
-->
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="android.appenumeration.syncadapter.shareduid.authority"
- android:accountType="android.appenumeration.account.type"
+ android:accountType="android.appenumeration.shareduid.account.type"
/>
diff --git a/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/DummyProvider.java b/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/DummyProvider.java
new file mode 100644
index 0000000..250c6f2
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/DummyProvider.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appenumeration.testapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class DummyProvider extends ContentProvider {
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ 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/tests/appenumeration/app/target/src/android/appenumeration/testapp/MockAuthenticatorService.java b/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/MockAuthenticatorService.java
new file mode 100644
index 0000000..45600ea
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/MockAuthenticatorService.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appenumeration.testapp;
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.NetworkErrorException;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+
+public class MockAuthenticatorService extends Service {
+
+ private static Authenticator sInstance;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (sInstance == null) {
+ sInstance = new Authenticator(getApplicationContext());
+ }
+ return sInstance.getIBinder();
+ }
+
+ private static class Authenticator extends AbstractAccountAuthenticator {
+
+ public Authenticator(Context context) {
+ super(context);
+ }
+
+ @Override
+ public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
+ return null;
+ }
+
+ @Override
+ public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
+ String authTokenType, String[] requiredFeatures, Bundle options)
+ throws NetworkErrorException {
+ return null;
+ }
+
+ @Override
+ public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account,
+ Bundle options) throws NetworkErrorException {
+ return null;
+ }
+
+ @Override
+ public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
+ String authTokenType, Bundle options) throws NetworkErrorException {
+ return null;
+ }
+
+ @Override
+ public String getAuthTokenLabel(String authTokenType) {
+ return null;
+ }
+
+ @Override
+ public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account,
+ String authTokenType, Bundle options) throws NetworkErrorException {
+ return null;
+ }
+
+ @Override
+ public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account,
+ String[] features) throws NetworkErrorException {
+ return null;
+ }
+ }
+}
diff --git a/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/MockSyncAdapterService.java b/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/MockSyncAdapterService.java
new file mode 100644
index 0000000..41fb62d
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/MockSyncAdapterService.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appenumeration.testapp;
+
+import android.accounts.Account;
+import android.app.Service;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SyncResult;
+import android.os.Bundle;
+import android.os.IBinder;
+
+public class MockSyncAdapterService extends Service {
+
+ private static SyncAdapter sInstance;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (sInstance == null) {
+ sInstance = new SyncAdapter(getApplicationContext());
+ }
+ return sInstance.getSyncAdapterBinder();
+ }
+
+ static class SyncAdapter extends AbstractThreadedSyncAdapter {
+
+ public SyncAdapter(Context context) {
+ super(context, false /* autoInitialize */);
+ }
+
+ @Override
+ public void onPerformSync(Account account, Bundle extras, String authority,
+ ContentProviderClient provider, SyncResult syncResult) {
+ }
+ }
+}
diff --git a/tests/tests/appenumeration/lib/Android.bp b/tests/tests/appenumeration/lib/Android.bp
index c608d2c..841f404 100644
--- a/tests/tests/appenumeration/lib/Android.bp
+++ b/tests/tests/appenumeration/lib/Android.bp
@@ -18,10 +18,6 @@
java_library {
name: "CtsAppEnumerationTestLib",
- srcs: ["src/**/*.java"],
-}
-
-java_library_host {
- name: "CtsAppEnumerationTestLibHost",
+ defaults: ["cts_support_defaults"],
srcs: ["src/**/*.java"],
}
diff --git a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
index c93016b..3910691 100644
--- a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
+++ b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
@@ -22,6 +22,8 @@
/** A package that queries for {@link #TARGET_NO_API} package */
public static final String QUERIES_PACKAGE = PKG_BASE + "queries.pkg";
+ /** A package has a provider that queries for {@link #TARGET_NO_API} package */
+ public static final String QUERIES_PACKAGE_PROVIDER = PKG_BASE + "queries.pkg.hasprovider";
/** Queries for the unexported authority in {@link #TARGET_FILTERS} provider */
public static final String QUERIES_UNEXPORTED_PROVIDER_AUTH =
PKG_BASE + "queries.provider.authority.unexported";
@@ -55,6 +57,10 @@
PKG_BASE + "queries.nothing.receives.uri";
public static final String QUERIES_NOTHING_RECEIVES_PERM_URI =
PKG_BASE + "queries.nothing.receives.perm.uri";
+ public static final String QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI =
+ PKG_BASE + "queries.nothing.receives.persistable.uri";
+ public static final String QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI =
+ PKG_BASE + "queries.nothing.receives.nonpersistable.uri";
/** Another package that has no queries tag or permission to query any specific packages */
public static final String QUERIES_NOTHING_SEES_INSTALLER =
PKG_BASE + "queries.nothing.sees.installer";
@@ -104,6 +110,9 @@
public static final String TARGET_SHARE = PKG_BASE + "share.activity";
/** A package that offers an activity that handles browsable web intents for a specific host */
public static final String TARGET_WEB = PKG_BASE + "web.activity";
+ /** A package that offers an activity acts as a browser, but use a prefix wildcard for host */
+ public static final String TARGET_PREFIX_WILDCARD_WEB =
+ PKG_BASE + "prefix.wildcard.web.activity";
/** A package that offers an activity acts as a browser with host undefined */
public static final String TARGET_BROWSER = PKG_BASE + "browser.activity";
/** A package that offers an activity acts as a browser, but uses a wildcard for host */
@@ -129,6 +138,10 @@
public static final String TARGET_FILTERS_APK = BASE_PATH + "CtsAppEnumerationFilters.apk";
public static final String QUERIES_NOTHING_SEES_INSTALLER_APK =
BASE_PATH + "CtsAppEnumerationQueriesNothingSeesInstaller.apk";
+ public static final String QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI_APK =
+ BASE_PATH + "CtsAppEnumerationQueriesNothingReceivesPersistableUri.apk";
+ public static final String QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI_APK =
+ BASE_PATH + "CtsAppEnumerationQueriesNothingReceivesNonPersistableUri.apk";
public static final String[] ALL_QUERIES_TARGETING_R_PACKAGES = {
QUERIES_NOTHING,
@@ -149,7 +162,7 @@
QUERIES_WILDCARD_WEB,
};
- public static final String ACTIVITY_CLASS_TEST = PKG_BASE + "cts.query.TestActivity";
+ public static final String ACTIVITY_CLASS_TEST = PKG_BASE + "cts.TestActivity";
public static final String ACTIVITY_CLASS_DUMMY_ACTIVITY = PKG_BASE + "testapp.DummyActivity";
public static final String ACTION_MANIFEST_ACTIVITY = PKG_BASE + "action.ACTIVITY";
@@ -173,7 +186,10 @@
PKG_BASE + "cts.action.AWAIT_PACKAGE_REMOVED";
public static final String ACTION_AWAIT_PACKAGE_ADDED =
PKG_BASE + "cts.action.AWAIT_PACKAGE_ADDED";
-
+ public static final String ACTION_AWAIT_PACKAGE_FULLY_REMOVED =
+ PKG_BASE + "cts.action.AWAIT_PACKAGE_FULLY_REMOVED";
+ public static final String ACTION_AWAIT_PACKAGE_DATA_CLEARED =
+ PKG_BASE + "cts.action.AWAIT_PACKAGE_DATA_CLEARED";
public static final String ACTION_QUERY_ACTIVITIES =
PKG_BASE + "cts.action.QUERY_INTENT_ACTIVITIES";
public static final String ACTION_QUERY_SERVICES =
@@ -193,10 +209,14 @@
PKG_BASE + "cts.action.GET_SYNCADAPTER_PACKAGES_FOR_AUTHORITY";
public static final String ACTION_GET_INSTALLED_APPWIDGET_PROVIDERS =
PKG_BASE + "cts.action.GET_INSTALLED_APPWIDGET_PROVIDERS";
+ public static final String ACTION_REQUEST_SYNC_AND_AWAIT_STATUS =
+ PKG_BASE + "cts.action.REQUEST_SYNC_AND_AWAIT_STATUS";
public static final String ACTION_AWAIT_PACKAGES_SUSPENDED =
PKG_BASE + "cts.action.AWAIT_PACKAGES_SUSPENDED";
public static final String ACTION_LAUNCHER_APPS_IS_ACTIVITY_ENABLED =
PKG_BASE + "cts.action.LAUNCHER_APPS_IS_ACTIVITY_ENABLED";
+ public static final String ACTION_LAUNCHER_APPS_GET_SUSPENDED_PACKAGE_LAUNCHER_EXTRAS =
+ PKG_BASE + "cts.action.LAUNCHER_APPS_GET_SUSPENDED_PACKAGE_LAUNCHER_EXTRAS";
public static final String ACTION_AWAIT_LAUNCHER_APPS_CALLBACK =
PKG_BASE + "cts.action.AWAIT_LAUNCHER_APPS_CALLBACK";
public static final String ACTION_GET_SHAREDLIBRARY_DEPENDENT_PACKAGES =
@@ -207,6 +227,35 @@
PKG_BASE + "cts.action.SET_INSTALLER_PACKAGE_NAME";
public static final String ACTION_GET_INSTALLED_ACCESSIBILITYSERVICES_PACKAGES =
PKG_BASE + "cts.action.GET_INSTALLED_ACCESSIBILITYSERVICES_PACKAGES";
+ public static final String ACTION_LAUNCHER_APPS_SHOULD_HIDE_FROM_SUGGESTIONS =
+ PKG_BASE + "cts.action.LAUNCHER_APPS_SHOULD_HIDE_FROM_SUGGESTIONS";
+ public static final String ACTION_CHECK_URI_PERMISSION =
+ PKG_BASE + "cts.action.CHECK_URI_PERMISSION";
+ public static final String ACTION_TAKE_PERSISTABLE_URI_PERMISSION =
+ PKG_BASE + "cts.action.TAKE_PERSISTABLE_URI_PERMISSION";
+ public static final String ACTION_CAN_PACKAGE_QUERY =
+ PKG_BASE + "cts.action.CAN_PACKAGE_QUERY";
+ public static final String ACTION_GET_ALL_PACKAGE_INSTALLER_SESSIONS =
+ PKG_BASE + "cts.action.GET_ALL_PACKAGE_INSTALLER_SESSIONS";
+ public static final String ACTION_AWAIT_LAUNCHER_APPS_SESSION_CALLBACK =
+ PKG_BASE + "cts.action.AWAIT_LAUNCHER_APPS_SESSION_CALLBACK";
+ public static final String ACTION_GET_SESSION_INFO =
+ PKG_BASE + "cts.action.GET_SESSION_INFO";
+ public static final String ACTION_GET_STAGED_SESSIONS =
+ PKG_BASE + "cts.action.GET_STAGED_SESSIONS";
+ public static final String ACTION_GET_ALL_SESSIONS =
+ PKG_BASE + "cts.action.GET_ALL_SESSIONS";
+ public static final String ACTION_PENDING_INTENT_GET_ACTIVITY =
+ PKG_BASE + "cts.action.PENDING_INTENT_GET_ACTIVITY";
+ public static final String ACTION_PENDING_INTENT_GET_CREATOR_PACKAGE =
+ PKG_BASE + "cts.action.PENDING_INTENT_GET_CREATOR_PACKAGE";
+ public static final String ACTION_CHECK_PACKAGE =
+ PKG_BASE + "cts.action.CHECK_PACKAGE";
+ public static final String ACTION_GRANT_URI_PERMISSION =
+ PKG_BASE + "cts.action.GRANT_URI_PERMISSION";
+ public static final String ACTION_REVOKE_URI_PERMISSION =
+ PKG_BASE + "cts.action.REVOKE_URI_PERMISSION";
+
public static final String EXTRA_REMOTE_CALLBACK = "remoteCallback";
public static final String EXTRA_REMOTE_READY_CALLBACK = "remoteReadyCallback";
public static final String EXTRA_ERROR = "error";
@@ -214,6 +263,9 @@
public static final String EXTRA_DATA = "data";
public static final String EXTRA_CERT = "cert";
public static final String EXTRA_AUTHORITY = "authority";
+ public static final String EXTRA_ACCOUNT = "account";
+ public static final String EXTRA_ID = "id";
+ public static final String EXTRA_PENDING_INTENT = "pendingIntent";
public static final int CALLBACK_EVENT_INVALID = -1;
public static final int CALLBACK_EVENT_PACKAGE_ADDED = 0;
diff --git a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/MissingCallbackException.java b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/MissingCallbackException.java
new file mode 100644
index 0000000..1efe1b7
--- /dev/null
+++ b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/MissingCallbackException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appenumeration.cts;
+
+public class MissingCallbackException extends Exception {
+
+ public MissingCallbackException(String action, long timeoutMs) {
+ super("The callback of " + action + " was not received within " + timeoutMs + "ms");
+ }
+}
diff --git a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/TestActivity.java b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/TestActivity.java
new file mode 100644
index 0000000..3c315a5
--- /dev/null
+++ b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/TestActivity.java
@@ -0,0 +1,972 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appenumeration.cts;
+
+import static android.appenumeration.cts.Constants.ACTION_CHECK_SIGNATURES;
+import static android.appenumeration.cts.Constants.ACTION_GET_INSTALLED_PACKAGES;
+import static android.appenumeration.cts.Constants.ACTION_GET_NAMES_FOR_UIDS;
+import static android.appenumeration.cts.Constants.ACTION_GET_NAME_FOR_UID;
+import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGES_FOR_UID;
+import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGE_INFO;
+import static android.appenumeration.cts.Constants.ACTION_HAS_SIGNING_CERTIFICATE;
+import static android.appenumeration.cts.Constants.ACTION_JUST_FINISH;
+import static android.appenumeration.cts.Constants.ACTION_QUERY_ACTIVITIES;
+import static android.appenumeration.cts.Constants.ACTION_QUERY_PROVIDERS;
+import static android.appenumeration.cts.Constants.ACTION_QUERY_SERVICES;
+import static android.appenumeration.cts.Constants.ACTION_SEND_RESULT;
+import static android.appenumeration.cts.Constants.ACTION_START_DIRECTLY;
+import static android.appenumeration.cts.Constants.ACTION_START_FOR_RESULT;
+import static android.appenumeration.cts.Constants.ACTION_START_SENDER_FOR_RESULT;
+import static android.appenumeration.cts.Constants.CALLBACK_EVENT_INVALID;
+import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGES_AVAILABLE;
+import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGES_SUSPENDED;
+import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGES_UNAVAILABLE;
+import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGES_UNSUSPENDED;
+import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_ADDED;
+import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_CHANGED;
+import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_REMOVED;
+import static android.appenumeration.cts.Constants.EXTRA_ACCOUNT;
+import static android.appenumeration.cts.Constants.EXTRA_AUTHORITY;
+import static android.appenumeration.cts.Constants.EXTRA_CERT;
+import static android.appenumeration.cts.Constants.EXTRA_DATA;
+import static android.appenumeration.cts.Constants.EXTRA_ERROR;
+import static android.appenumeration.cts.Constants.EXTRA_FLAGS;
+import static android.appenumeration.cts.Constants.EXTRA_ID;
+import static android.appenumeration.cts.Constants.EXTRA_PENDING_INTENT;
+import static android.appenumeration.cts.Constants.EXTRA_REMOTE_CALLBACK;
+import static android.appenumeration.cts.Constants.EXTRA_REMOTE_READY_CALLBACK;
+import static android.content.Intent.EXTRA_COMPONENT_NAME;
+import static android.content.Intent.EXTRA_PACKAGES;
+import static android.content.Intent.EXTRA_RETURN_RESULT;
+import static android.content.Intent.EXTRA_UID;
+import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
+import static android.os.Process.INVALID_UID;
+import static android.os.Process.ROOT_UID;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.ServiceConnection;
+import android.content.SyncAdapterType;
+import android.content.SyncStatusObserver;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller.SessionCallback;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Parcelable;
+import android.os.PatternMatcher;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.UserHandle;
+import android.util.SparseArray;
+import android.view.accessibility.AccessibilityManager;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * A test activity running in the query and target applications.
+ */
+public class TestActivity extends Activity {
+
+ private final static long TIMEOUT_MS = 3000;
+
+ /**
+ * Extending the timeout time of non broadcast receivers, avoid not
+ * receiving callbacks in time on some common low-end platforms and
+ * do not affect the situation that callback can be received in advance.
+ */
+ private final static long EXTENDED_TIMEOUT_MS = 5000;
+
+ SparseArray<RemoteCallback> callbacks = new SparseArray<>();
+
+ private Handler mainHandler;
+ private Handler backgroundHandler;
+ private HandlerThread backgroundThread;
+ private Object syncStatusHandle;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ mainHandler = new Handler(getMainLooper());
+ backgroundThread = new HandlerThread("testBackground");
+ backgroundThread.start();
+ backgroundHandler = new Handler(backgroundThread.getLooper());
+ super.onCreate(savedInstanceState);
+ handleIntent(getIntent());
+ onCommandReady(getIntent());
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (syncStatusHandle != null) {
+ ContentResolver.removeStatusChangeListener(syncStatusHandle);
+ }
+ backgroundThread.quitSafely();
+ super.onDestroy();
+ }
+
+ private void handleIntent(Intent intent) {
+ RemoteCallback remoteCallback = intent.getParcelableExtra(EXTRA_REMOTE_CALLBACK);
+ try {
+ final String action = intent.getAction();
+ final Intent queryIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+ if (ACTION_GET_PACKAGE_INFO.equals(action)) {
+ final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ sendPackageInfo(remoteCallback, packageName);
+ } else if (ACTION_GET_PACKAGES_FOR_UID.equals(action)) {
+ final int uid = intent.getIntExtra(EXTRA_UID, INVALID_UID);
+ sendPackagesForUid(remoteCallback, uid);
+ } else if (ACTION_GET_NAME_FOR_UID.equals(action)) {
+ final int uid = intent.getIntExtra(EXTRA_UID, INVALID_UID);
+ sendNameForUid(remoteCallback, uid);
+ } else if (ACTION_GET_NAMES_FOR_UIDS.equals(action)) {
+ final int uid = intent.getIntExtra(EXTRA_UID, INVALID_UID);
+ sendNamesForUids(remoteCallback, uid);
+ } else if (ACTION_CHECK_SIGNATURES.equals(action)) {
+ final int uid1 = getPackageManager().getApplicationInfo(
+ getPackageName(), /* flags */ 0).uid;
+ final int uid2 = intent.getIntExtra(EXTRA_UID, INVALID_UID);
+ sendCheckSignatures(remoteCallback, uid1, uid2);
+ } else if (ACTION_HAS_SIGNING_CERTIFICATE.equals(action)) {
+ final int uid = intent.getIntExtra(EXTRA_UID, INVALID_UID);
+ final byte[] cert = intent.getBundleExtra(EXTRA_DATA).getByteArray(EXTRA_CERT);
+ sendHasSigningCertificate(remoteCallback, uid, cert, CERT_INPUT_RAW_X509);
+ } else if (ACTION_START_FOR_RESULT.equals(action)) {
+ final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ int requestCode = RESULT_FIRST_USER + callbacks.size();
+ callbacks.put(requestCode, remoteCallback);
+ startActivityForResult(
+ new Intent(ACTION_SEND_RESULT).setComponent(
+ new ComponentName(packageName, getClass().getCanonicalName())),
+ requestCode);
+ // don't send anything... await result callback
+ } else if (ACTION_SEND_RESULT.equals(action)) {
+ try {
+ setResult(RESULT_OK,
+ getIntent().putExtra(
+ Intent.EXTRA_RETURN_RESULT,
+ getPackageManager().getPackageInfo(getCallingPackage(), 0)));
+ } catch (PackageManager.NameNotFoundException e) {
+ setResult(RESULT_FIRST_USER, new Intent().putExtra("error", e));
+ }
+ finish();
+ } else if (ACTION_QUERY_ACTIVITIES.equals(action)) {
+ sendQueryIntentActivities(remoteCallback, queryIntent);
+ } else if (ACTION_QUERY_SERVICES.equals(action)) {
+ sendQueryIntentServices(remoteCallback, queryIntent);
+ } else if (ACTION_QUERY_PROVIDERS.equals(action)) {
+ sendQueryIntentProviders(remoteCallback, queryIntent);
+ } else if (ACTION_START_DIRECTLY.equals(action)) {
+ try {
+ startActivity(queryIntent);
+ remoteCallback.sendResult(new Bundle());
+ } catch (ActivityNotFoundException e) {
+ sendError(remoteCallback, e);
+ }
+ finish();
+ } else if (ACTION_JUST_FINISH.equals(action)) {
+ finish();
+ } else if (ACTION_GET_INSTALLED_PACKAGES.equals(action)) {
+ sendGetInstalledPackages(remoteCallback, queryIntent.getIntExtra(EXTRA_FLAGS, 0));
+ } else if (ACTION_START_SENDER_FOR_RESULT.equals(action)) {
+ PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
+ int requestCode = RESULT_FIRST_USER + callbacks.size();
+ callbacks.put(requestCode, remoteCallback);
+ try {
+ startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, null,
+ 0, 0, 0);
+ } catch (IntentSender.SendIntentException e) {
+ sendError(remoteCallback, e);
+ }
+ } else if (Constants.ACTION_AWAIT_PACKAGE_REMOVED.equals(action)) {
+ final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ awaitPackageBroadcast(
+ remoteCallback, packageName, Intent.ACTION_PACKAGE_REMOVED, TIMEOUT_MS);
+ } else if (Constants.ACTION_AWAIT_PACKAGE_ADDED.equals(action)) {
+ final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ awaitPackageBroadcast(
+ remoteCallback, packageName, Intent.ACTION_PACKAGE_ADDED, TIMEOUT_MS);
+ } else if (Constants.ACTION_AWAIT_PACKAGE_FULLY_REMOVED.equals(action)) {
+ final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ awaitPackageBroadcast(remoteCallback, packageName,
+ Intent.ACTION_PACKAGE_FULLY_REMOVED, TIMEOUT_MS);
+ } else if (Constants.ACTION_AWAIT_PACKAGE_DATA_CLEARED.equals(action)) {
+ final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ awaitPackageBroadcast(remoteCallback, packageName,
+ Intent.ACTION_PACKAGE_DATA_CLEARED, TIMEOUT_MS);
+ } else if (Constants.ACTION_QUERY_RESOLVER.equals(action)) {
+ final String authority = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ queryResolverForVisiblePackages(remoteCallback, authority);
+ } else if (Constants.ACTION_BIND_SERVICE.equals(action)) {
+ final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ bindService(remoteCallback, packageName);
+ } else if (Constants.ACTION_GET_SYNCADAPTER_TYPES.equals(action)) {
+ sendSyncAdapterTypes(remoteCallback);
+ } else if (Constants.ACTION_GET_INSTALLED_APPWIDGET_PROVIDERS.equals(action)) {
+ sendInstalledAppWidgetProviders(remoteCallback);
+ } else if (Constants.ACTION_AWAIT_PACKAGES_SUSPENDED.equals(action)) {
+ final String[] awaitPackages = intent.getBundleExtra(EXTRA_DATA)
+ .getStringArray(EXTRA_PACKAGES);
+ awaitSuspendedPackagesBroadcast(remoteCallback, Arrays.asList(awaitPackages),
+ Intent.ACTION_PACKAGES_SUSPENDED, TIMEOUT_MS);
+ } else if (Constants.ACTION_LAUNCHER_APPS_IS_ACTIVITY_ENABLED.equals(action)) {
+ final String componentName = intent.getBundleExtra(EXTRA_DATA)
+ .getString(EXTRA_COMPONENT_NAME);
+ sendIsActivityEnabled(remoteCallback, ComponentName.unflattenFromString(
+ componentName));
+ } else if (Constants.ACTION_LAUNCHER_APPS_GET_SUSPENDED_PACKAGE_LAUNCHER_EXTRAS.equals(
+ action)) {
+ final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ sendGetSuspendedPackageLauncherExtras(remoteCallback, packageName);
+ } else if (Constants.ACTION_GET_SYNCADAPTER_PACKAGES_FOR_AUTHORITY.equals(action)) {
+ final String authority = intent.getBundleExtra(EXTRA_DATA)
+ .getString(EXTRA_AUTHORITY);
+ final int userId = intent.getBundleExtra(EXTRA_DATA)
+ .getInt(Intent.EXTRA_USER);
+ sendSyncAdapterPackagesForAuthorityAsUser(remoteCallback, authority, userId);
+ } else if (Constants.ACTION_REQUEST_SYNC_AND_AWAIT_STATUS.equals(action)) {
+ final String authority = intent.getBundleExtra(EXTRA_DATA)
+ .getString(EXTRA_AUTHORITY);
+ final Account account = intent.getBundleExtra(EXTRA_DATA)
+ .getParcelable(EXTRA_ACCOUNT);
+ awaitRequestSyncStatus(remoteCallback, action, account, authority,
+ EXTENDED_TIMEOUT_MS);
+ } else if (Constants.ACTION_AWAIT_LAUNCHER_APPS_CALLBACK.equals(action)) {
+ final int expectedEventCode = intent.getBundleExtra(EXTRA_DATA)
+ .getInt(EXTRA_FLAGS, CALLBACK_EVENT_INVALID);
+ awaitLauncherAppsCallback(remoteCallback, expectedEventCode, EXTENDED_TIMEOUT_MS);
+ } else if (Constants.ACTION_GET_SHAREDLIBRARY_DEPENDENT_PACKAGES.equals(action)) {
+ final String sharedLibName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ sendGetSharedLibraryDependentPackages(remoteCallback, sharedLibName);
+ } else if (Constants.ACTION_GET_PREFERRED_ACTIVITIES.equals(action)) {
+ sendGetPreferredActivities(remoteCallback);
+ } else if (Constants.ACTION_SET_INSTALLER_PACKAGE_NAME.equals(action)) {
+ final String targetPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ final String installerPackageName = intent.getBundleExtra(EXTRA_DATA)
+ .getString(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
+ sendSetInstallerPackageName(remoteCallback, targetPackageName,
+ installerPackageName);
+ } else if (Constants.ACTION_GET_INSTALLED_ACCESSIBILITYSERVICES_PACKAGES.equals(
+ action)) {
+ sendGetInstalledAccessibilityServicePackages(remoteCallback);
+ } else if (Constants.ACTION_LAUNCHER_APPS_SHOULD_HIDE_FROM_SUGGESTIONS.equals(action)) {
+ final String targetPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ final int userId = intent.getBundleExtra(EXTRA_DATA).getInt(Intent.EXTRA_USER);
+ sendLauncherAppsShouldHideFromSuggestions(remoteCallback, targetPackageName,
+ userId);
+ } else if (Constants.ACTION_CHECK_URI_PERMISSION.equals(action)) {
+ final String targetPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ final int targetUid = intent.getIntExtra(EXTRA_UID, INVALID_UID);
+ final String sourceAuthority = intent.getBundleExtra(EXTRA_DATA)
+ .getString(EXTRA_AUTHORITY);
+ sendCheckUriPermission(remoteCallback, sourceAuthority, targetPackageName,
+ targetUid);
+ } else if (Constants.ACTION_TAKE_PERSISTABLE_URI_PERMISSION.equals(action)) {
+ final Uri uri = intent.getData();
+ final int modeFlags = intent.getFlags();
+ if (uri != null) {
+ getContentResolver().takePersistableUriPermission(uri, modeFlags);
+ }
+ finish();
+ } else if (Constants.ACTION_CAN_PACKAGE_QUERY.equals(action)) {
+ final String sourcePackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ final String targetPackageName = intent.getBundleExtra(EXTRA_DATA)
+ .getString(Intent.EXTRA_PACKAGE_NAME);
+ sendCanPackageQuery(remoteCallback, sourcePackageName, targetPackageName);
+ } else if (Constants.ACTION_GET_ALL_PACKAGE_INSTALLER_SESSIONS.equals(action)) {
+ final List<SessionInfo> infos = getSystemService(LauncherApps.class)
+ .getAllPackageInstallerSessions();
+ sendSessionInfosListResult(remoteCallback, infos);
+ } else if (Constants.ACTION_AWAIT_LAUNCHER_APPS_SESSION_CALLBACK.equals(action)) {
+ final int expectedEventCode = intent.getBundleExtra(EXTRA_DATA)
+ .getInt(EXTRA_ID, SessionInfo.INVALID_ID);
+ awaitLauncherAppsSessionCallback(remoteCallback, expectedEventCode,
+ EXTENDED_TIMEOUT_MS);
+ } else if (Constants.ACTION_GET_SESSION_INFO.equals(action)) {
+ final int sessionId = intent.getBundleExtra(EXTRA_DATA)
+ .getInt(EXTRA_ID, SessionInfo.INVALID_ID);
+ final List<SessionInfo> infos = Arrays.asList(getPackageManager()
+ .getPackageInstaller()
+ .getSessionInfo(sessionId));
+ sendSessionInfosListResult(remoteCallback, infos);
+ } else if (Constants.ACTION_GET_STAGED_SESSIONS.equals(action)) {
+ final List<SessionInfo> infos = getPackageManager().getPackageInstaller()
+ .getStagedSessions();
+ sendSessionInfosListResult(remoteCallback, infos);
+ } else if (Constants.ACTION_GET_ALL_SESSIONS.equals(action)) {
+ final List<SessionInfo> infos = getPackageManager().getPackageInstaller()
+ .getAllSessions();
+ sendSessionInfosListResult(remoteCallback, infos);
+ } else if (Constants.ACTION_PENDING_INTENT_GET_ACTIVITY.equals(action)) {
+ sendPendingIntentGetActivity(remoteCallback);
+ } else if (Constants.ACTION_PENDING_INTENT_GET_CREATOR_PACKAGE.equals(action)) {
+ sendPendingIntentGetCreatorPackage(remoteCallback,
+ intent.getParcelableExtra(EXTRA_PENDING_INTENT));
+ } else if (Constants.ACTION_CHECK_PACKAGE.equals(action)) {
+ // Using ROOT_UID as default value here to pass the check in #verifyAndGetBypass,
+ // this is intended by design.
+ final int uid = intent.getIntExtra(EXTRA_UID, ROOT_UID);
+ final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ sendCheckPackageResult(remoteCallback, packageName, uid);
+ } else if (Constants.ACTION_GRANT_URI_PERMISSION.equals(action)) {
+ final String targetPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ final String sourceAuthority = intent.getBundleExtra(EXTRA_DATA)
+ .getString(EXTRA_AUTHORITY);
+ sendGrantUriPermission(remoteCallback, sourceAuthority, targetPackageName);
+ } else if (Constants.ACTION_REVOKE_URI_PERMISSION.equals(action)) {
+ final String sourceAuthority = intent.getBundleExtra(EXTRA_DATA)
+ .getString(EXTRA_AUTHORITY);
+ sendRevokeUriPermission(remoteCallback, sourceAuthority);
+ } else {
+ sendError(remoteCallback, new Exception("unknown action " + action));
+ }
+ } catch (Exception e) {
+ sendError(remoteCallback, e);
+ }
+ }
+
+ private void sendGetInstalledAccessibilityServicePackages(RemoteCallback remoteCallback) {
+ final String[] packages = getSystemService(
+ AccessibilityManager.class).getInstalledAccessibilityServiceList().stream().map(
+ p -> p.getComponentName().getPackageName()).distinct().toArray(String[]::new);
+ final Bundle result = new Bundle();
+ result.putStringArray(EXTRA_RETURN_RESULT, packages);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void onCommandReady(Intent intent) {
+ final RemoteCallback callback = intent.getParcelableExtra(EXTRA_REMOTE_READY_CALLBACK);
+ if (callback != null) {
+ callback.sendResult(null);
+ }
+ }
+
+ private void awaitPackageBroadcast(RemoteCallback remoteCallback, String packageName,
+ String action, long timeoutMs) {
+ final IntentFilter filter = new IntentFilter(action);
+ filter.addDataScheme("package");
+ filter.addDataSchemeSpecificPart(packageName, PatternMatcher.PATTERN_LITERAL);
+ final Object token = new Object();
+ registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Bundle result = new Bundle();
+ result.putString(EXTRA_DATA, intent.getDataString());
+ remoteCallback.sendResult(result);
+ mainHandler.removeCallbacksAndMessages(token);
+ finish();
+ }
+ }, filter, Context.RECEIVER_EXPORTED_UNAUDITED);
+ mainHandler.postDelayed(
+ () -> sendError(remoteCallback,
+ new MissingBroadcastException(action, timeoutMs)),
+ token, timeoutMs);
+ }
+
+ private void awaitSuspendedPackagesBroadcast(RemoteCallback remoteCallback,
+ List<String> awaitList, String action, long timeoutMs) {
+ final IntentFilter filter = new IntentFilter(action);
+ final ArrayList<String> suspendedList = new ArrayList<>();
+ final Object token = new Object();
+ final Runnable sendResult = () -> {
+ final Bundle result = new Bundle();
+ result.putStringArray(EXTRA_PACKAGES, suspendedList.toArray(new String[] {}));
+ remoteCallback.sendResult(result);
+ finish();
+ };
+ registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Bundle extras = intent.getExtras();
+ final String[] changedList = extras.getStringArray(
+ Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ suspendedList.addAll(Arrays.stream(changedList).filter(
+ p -> awaitList.contains(p)).collect(Collectors.toList()));
+ if (suspendedList.size() == awaitList.size()) {
+ mainHandler.removeCallbacksAndMessages(token);
+ sendResult.run();
+ }
+ }
+ }, filter, Context.RECEIVER_EXPORTED_UNAUDITED);
+ mainHandler.postDelayed(() -> sendResult.run(), token, timeoutMs);
+ }
+
+ private void awaitLauncherAppsCallback(RemoteCallback remoteCallback, int expectedEventCode,
+ long timeoutMs) {
+ final Object token = new Object();
+ final Bundle result = new Bundle();
+ final LauncherApps launcherApps = getSystemService(LauncherApps.class);
+ final LauncherApps.Callback launcherAppsCallback = new LauncherApps.Callback() {
+
+ private void onPackageStateUpdated(String[] packageNames, int resultCode) {
+ if (resultCode != expectedEventCode) {
+ return;
+ }
+
+ mainHandler.removeCallbacksAndMessages(token);
+ result.putStringArray(EXTRA_PACKAGES, packageNames);
+ result.putInt(EXTRA_FLAGS, resultCode);
+ remoteCallback.sendResult(result);
+
+ launcherApps.unregisterCallback(this);
+ finish();
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, UserHandle user) {
+ onPackageStateUpdated(new String[]{packageName}, CALLBACK_EVENT_PACKAGE_REMOVED);
+ }
+
+ @Override
+ public void onPackageAdded(String packageName, UserHandle user) {
+ onPackageStateUpdated(new String[]{packageName}, CALLBACK_EVENT_PACKAGE_ADDED);
+ }
+
+ @Override
+ public void onPackageChanged(String packageName, UserHandle user) {
+ onPackageStateUpdated(new String[]{packageName}, CALLBACK_EVENT_PACKAGE_CHANGED);
+ }
+
+ @Override
+ public void onPackagesAvailable(String[] packageNames, UserHandle user,
+ boolean replacing) {
+ onPackageStateUpdated(packageNames, CALLBACK_EVENT_PACKAGES_AVAILABLE);
+ }
+
+ @Override
+ public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+ boolean replacing) {
+ onPackageStateUpdated(packageNames, CALLBACK_EVENT_PACKAGES_UNAVAILABLE);
+ }
+
+ @Override
+ public void onPackagesSuspended(String[] packageNames, UserHandle user) {
+ onPackageStateUpdated(packageNames, CALLBACK_EVENT_PACKAGES_SUSPENDED);
+ super.onPackagesSuspended(packageNames, user);
+ }
+
+ @Override
+ public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
+ onPackageStateUpdated(packageNames, CALLBACK_EVENT_PACKAGES_UNSUSPENDED);
+ super.onPackagesUnsuspended(packageNames, user);
+ }
+ };
+
+ launcherApps.registerCallback(launcherAppsCallback);
+
+ mainHandler.postDelayed(() -> {
+ result.putStringArray(EXTRA_PACKAGES, new String[]{});
+ result.putInt(EXTRA_FLAGS, CALLBACK_EVENT_INVALID);
+ remoteCallback.sendResult(result);
+
+ launcherApps.unregisterCallback(launcherAppsCallback);
+ finish();
+ }, token, timeoutMs);
+ }
+
+ private void sendGetInstalledPackages(RemoteCallback remoteCallback, int flags) {
+ String[] packages =
+ getPackageManager().getInstalledPackages(flags)
+ .stream().map(p -> p.packageName).distinct().toArray(String[]::new);
+ Bundle result = new Bundle();
+ result.putStringArray(EXTRA_RETURN_RESULT, packages);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendQueryIntentActivities(RemoteCallback remoteCallback, Intent queryIntent) {
+ final String[] resolveInfos = getPackageManager().queryIntentActivities(
+ queryIntent, 0 /* flags */).stream()
+ .map(ri -> ri.activityInfo.applicationInfo.packageName)
+ .distinct()
+ .toArray(String[]::new);
+ Bundle result = new Bundle();
+ result.putStringArray(EXTRA_RETURN_RESULT, resolveInfos);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendQueryIntentServices(RemoteCallback remoteCallback, Intent queryIntent) {
+ final String[] resolveInfos = getPackageManager().queryIntentServices(
+ queryIntent, 0 /* flags */).stream()
+ .map(ri -> ri.serviceInfo.applicationInfo.packageName)
+ .distinct()
+ .toArray(String[]::new);
+ Bundle result = new Bundle();
+ result.putStringArray(EXTRA_RETURN_RESULT, resolveInfos);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendQueryIntentProviders(RemoteCallback remoteCallback, Intent queryIntent) {
+ final String[] resolveInfos = getPackageManager().queryIntentContentProviders(
+ queryIntent, 0 /* flags */).stream()
+ .map(ri -> ri.providerInfo.applicationInfo.packageName)
+ .distinct()
+ .toArray(String[]::new);
+ Bundle result = new Bundle();
+ result.putStringArray(EXTRA_RETURN_RESULT, resolveInfos);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void queryResolverForVisiblePackages(RemoteCallback remoteCallback, String authority) {
+ backgroundHandler.post(() -> {
+ Uri queryUri = Uri.parse("content://" + authority + "/test");
+ Cursor query = getContentResolver().query(queryUri, null, null, null, null);
+ if (query == null || !query.moveToFirst()) {
+ sendError(remoteCallback,
+ new IllegalStateException(
+ "Query of " + queryUri + " could not be completed"));
+ return;
+ }
+ ArrayList<String> visiblePackages = new ArrayList<>();
+ while (!query.isAfterLast()) {
+ visiblePackages.add(query.getString(0));
+ query.moveToNext();
+ }
+ query.close();
+
+ mainHandler.post(() -> {
+ Bundle result = new Bundle();
+ result.putStringArray(EXTRA_RETURN_RESULT, visiblePackages.toArray(new String[]{}));
+ remoteCallback.sendResult(result);
+ finish();
+ });
+
+ });
+ }
+
+ private void sendError(RemoteCallback remoteCallback, Exception failure) {
+ Bundle result = new Bundle();
+ result.putSerializable(EXTRA_ERROR, failure);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendPackageInfo(RemoteCallback remoteCallback, String packageName) {
+ final PackageInfo pi;
+ try {
+ pi = getPackageManager().getPackageInfo(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ sendError(remoteCallback, e);
+ return;
+ }
+ Bundle result = new Bundle();
+ result.putParcelable(EXTRA_RETURN_RESULT, pi);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendPackagesForUid(RemoteCallback remoteCallback, int uid) {
+ final String[] packages = getPackageManager().getPackagesForUid(uid);
+ final Bundle result = new Bundle();
+ result.putStringArray(EXTRA_RETURN_RESULT, packages);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendNameForUid(RemoteCallback remoteCallback, int uid) {
+ final String name = getPackageManager().getNameForUid(uid);
+ final Bundle result = new Bundle();
+ result.putString(EXTRA_RETURN_RESULT, name);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendNamesForUids(RemoteCallback remoteCallback, int uid) {
+ final String[] names = getPackageManager().getNamesForUids(new int[]{uid});
+ final Bundle result = new Bundle();
+ result.putStringArray(EXTRA_RETURN_RESULT, names);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendCheckSignatures(RemoteCallback remoteCallback, int uid1, int uid2) {
+ final int signatureResult = getPackageManager().checkSignatures(uid1, uid2);
+ final Bundle result = new Bundle();
+ result.putInt(EXTRA_RETURN_RESULT, signatureResult);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendHasSigningCertificate(RemoteCallback remoteCallback, int uid, byte[] cert,
+ int type) {
+ final boolean signatureResult = getPackageManager().hasSigningCertificate(uid, cert, type);
+ final Bundle result = new Bundle();
+ result.putBoolean(EXTRA_RETURN_RESULT, signatureResult);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ /**
+ * Instead of sending a list of package names, this function sends a List of
+ * {@link SyncAdapterType}, since the {@link SyncAdapterType#getPackageName()} is a test api
+ * which can only be invoked in the instrumentation.
+ */
+ private void sendSyncAdapterTypes(RemoteCallback remoteCallback) {
+ final SyncAdapterType[] types = ContentResolver.getSyncAdapterTypes();
+ final ArrayList<Parcelable> parcelables = new ArrayList<>();
+ for (SyncAdapterType type : types) {
+ parcelables.add(type);
+ }
+ final Bundle result = new Bundle();
+ result.putParcelableArrayList(EXTRA_RETURN_RESULT, parcelables);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendIsActivityEnabled(RemoteCallback remoteCallback, ComponentName componentName) {
+ final LauncherApps launcherApps = getSystemService(LauncherApps.class);
+ final Bundle result = new Bundle();
+ try {
+ result.putBoolean(EXTRA_RETURN_RESULT, launcherApps.isActivityEnabled(componentName,
+ Process.myUserHandle()));
+ } catch (IllegalArgumentException e) {
+ }
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendGetSuspendedPackageLauncherExtras(RemoteCallback remoteCallback,
+ String packageName) {
+ final LauncherApps launcherApps = getSystemService(LauncherApps.class);
+ final Bundle result = new Bundle();
+ try {
+ result.putBundle(EXTRA_RETURN_RESULT,
+ launcherApps.getSuspendedPackageLauncherExtras(packageName,
+ Process.myUserHandle()));
+ } catch (IllegalArgumentException e) {
+ }
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendInstalledAppWidgetProviders(RemoteCallback remoteCallback) {
+ final AppWidgetManager appWidgetManager = getSystemService(AppWidgetManager.class);
+ final List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
+ final ArrayList<Parcelable> parcelables = new ArrayList<>();
+ for (AppWidgetProviderInfo info : providers) {
+ parcelables.add(info);
+ }
+ final Bundle result = new Bundle();
+ result.putParcelableArrayList(EXTRA_RETURN_RESULT, parcelables);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendSyncAdapterPackagesForAuthorityAsUser(RemoteCallback remoteCallback,
+ String authority, int userId) {
+ final String[] syncAdapterPackages = ContentResolver
+ .getSyncAdapterPackagesForAuthorityAsUser(authority, userId);
+ final Bundle result = new Bundle();
+ result.putStringArray(Intent.EXTRA_PACKAGES, syncAdapterPackages);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void awaitRequestSyncStatus(RemoteCallback remoteCallback, String action,
+ Account account, String authority, long timeoutMs) {
+ ContentResolver.cancelSync(account, authority);
+ final Object token = new Object();
+ final SyncStatusObserver observer = which -> {
+ final Bundle result = new Bundle();
+ result.putBoolean(EXTRA_RETURN_RESULT, true);
+ remoteCallback.sendResult(result);
+ mainHandler.removeCallbacksAndMessages(token);
+ finish();
+ };
+ syncStatusHandle = ContentResolver.addStatusChangeListener(
+ ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE
+ | ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS
+ | ContentResolver.SYNC_OBSERVER_TYPE_PENDING, observer);
+
+ ContentResolver.requestSync(account, authority, new Bundle());
+ mainHandler.postDelayed(
+ () -> sendError(remoteCallback, new MissingCallbackException(action, timeoutMs)),
+ token, timeoutMs);
+ }
+
+ private void sendGetSharedLibraryDependentPackages(RemoteCallback remoteCallback,
+ String sharedLibName) {
+ final List<SharedLibraryInfo> sharedLibraryInfos = getPackageManager()
+ .getSharedLibraries(0 /* flags */);
+ SharedLibraryInfo sharedLibraryInfo = sharedLibraryInfos.stream().filter(
+ info -> sharedLibName.equals(info.getName())).findAny().orElse(null);
+ final String[] dependentPackages = sharedLibraryInfo == null ? null
+ : sharedLibraryInfo.getDependentPackages().stream()
+ .map(versionedPackage -> versionedPackage.getPackageName())
+ .distinct().collect(Collectors.toList()).toArray(new String[]{});
+ final Bundle result = new Bundle();
+ result.putStringArray(Intent.EXTRA_PACKAGES, dependentPackages);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendGetPreferredActivities(RemoteCallback remoteCallback) {
+ final List<IntentFilter> filters = new ArrayList<>();
+ final List<ComponentName> activities = new ArrayList<>();
+ getPackageManager().getPreferredActivities(filters, activities, null /* packageName*/);
+ final String[] packages = activities.stream()
+ .map(componentName -> componentName.getPackageName()).distinct()
+ .collect(Collectors.toList()).toArray(new String[]{});
+ final Bundle result = new Bundle();
+ result.putStringArray(Intent.EXTRA_PACKAGES, packages);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendSetInstallerPackageName(RemoteCallback remoteCallback,
+ String targetPackageName, String installerPackageName) {
+ try {
+ getPackageManager().setInstallerPackageName(targetPackageName, installerPackageName);
+ remoteCallback.sendResult(null);
+ finish();
+ } catch (Exception e) {
+ sendError(remoteCallback, e);
+ }
+ }
+
+ private void sendLauncherAppsShouldHideFromSuggestions(RemoteCallback remoteCallback,
+ String targetPackageName, int userId) {
+ final LauncherApps launcherApps = getSystemService(LauncherApps.class);
+ final boolean hideFromSuggestions = launcherApps.shouldHideFromSuggestions(
+ targetPackageName, UserHandle.of(userId));
+ final Bundle result = new Bundle();
+ result.putBoolean(EXTRA_RETURN_RESULT, hideFromSuggestions);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendCheckUriPermission(RemoteCallback remoteCallback, String sourceAuthority,
+ String targetPackageName, int targetUid) {
+ final Uri uri = Uri.parse("content://" + sourceAuthority);
+ grantUriPermission(targetPackageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ final int permissionResult = checkUriPermission(uri, 0 /* pid */, targetUid,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ final Bundle result = new Bundle();
+ result.putInt(EXTRA_RETURN_RESULT, permissionResult);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendGrantUriPermission(RemoteCallback remoteCallback, String sourceAuthority,
+ String targetPackageName) {
+ final Uri uri = Uri.parse("content://" + sourceAuthority);
+ grantUriPermission(targetPackageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ remoteCallback.sendResult(null);
+ finish();
+ }
+
+ private void sendRevokeUriPermission(RemoteCallback remoteCallback, String sourceAuthority) {
+ final Uri uri = Uri.parse("content://" + sourceAuthority);
+ revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ remoteCallback.sendResult(null);
+ finish();
+ }
+
+ private void sendCanPackageQuery(RemoteCallback remoteCallback, String sourcePackageName,
+ String targetPackageName) {
+ try {
+ final boolean visibility = getPackageManager().canPackageQuery(sourcePackageName,
+ targetPackageName);
+ final Bundle result = new Bundle();
+ result.putBoolean(EXTRA_RETURN_RESULT, visibility);
+ remoteCallback.sendResult(result);
+ finish();
+ } catch (PackageManager.NameNotFoundException e) {
+ sendError(remoteCallback, e);
+ }
+ }
+
+ private void sendSessionInfosListResult(RemoteCallback remoteCallback,
+ List<SessionInfo> infos) {
+ final ArrayList<Parcelable> parcelables = new ArrayList<>(infos);
+ for (SessionInfo info : infos) {
+ parcelables.add(info);
+ }
+ final Bundle result = new Bundle();
+ result.putParcelableArrayList(EXTRA_RETURN_RESULT, parcelables);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void awaitLauncherAppsSessionCallback(RemoteCallback remoteCallback,
+ int expectedSessionId, long timeoutMs) {
+ final Object token = new Object();
+ final Bundle result = new Bundle();
+ final LauncherApps launcherApps = getSystemService(LauncherApps.class);
+ final SessionCallback sessionCallback = new SessionCallback() {
+
+ @Override
+ public void onCreated(int sessionId) {
+ // No-op
+ }
+
+ @Override
+ public void onBadgingChanged(int sessionId) {
+ // No-op
+ }
+
+ @Override
+ public void onActiveChanged(int sessionId, boolean active) {
+ // No-op
+ }
+
+ @Override
+ public void onProgressChanged(int sessionId, float progress) {
+ // No-op
+ }
+
+ @Override
+ public void onFinished(int sessionId, boolean success) {
+ if (sessionId != expectedSessionId) {
+ return;
+ }
+
+ mainHandler.removeCallbacksAndMessages(token);
+ result.putInt(EXTRA_ID, sessionId);
+ remoteCallback.sendResult(result);
+
+ launcherApps.unregisterPackageInstallerSessionCallback(this);
+ finish();
+ }
+ };
+
+ launcherApps.registerPackageInstallerSessionCallback(this.getMainExecutor(),
+ sessionCallback);
+
+ mainHandler.postDelayed(() -> {
+ result.putInt(EXTRA_ID, SessionInfo.INVALID_ID);
+ remoteCallback.sendResult(result);
+
+ launcherApps.unregisterPackageInstallerSessionCallback(sessionCallback);
+ finish();
+ }, token, timeoutMs);
+ }
+
+ private void sendPendingIntentGetActivity(RemoteCallback remoteCallback) {
+ final PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* requestCode */,
+ new Intent(this, TestActivity.class), PendingIntent.FLAG_IMMUTABLE);
+ final Bundle result = new Bundle();
+ result.putParcelable(EXTRA_PENDING_INTENT, pendingIntent);
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendPendingIntentGetCreatorPackage(RemoteCallback remoteCallback,
+ PendingIntent pendingIntent) {
+ final Bundle result = new Bundle();
+ result.putString(Intent.EXTRA_PACKAGE_NAME, pendingIntent.getCreatorPackage());
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void sendCheckPackageResult(RemoteCallback remoteCallback, String packageName,
+ int uid) {
+ try {
+ getSystemService(AppOpsManager.class).checkPackage(uid, packageName);
+ final Bundle result = new Bundle();
+ result.putBoolean(EXTRA_RETURN_RESULT, true);
+ remoteCallback.sendResult(result);
+ finish();
+ } catch (SecurityException e) {
+ sendError(remoteCallback, e);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ final RemoteCallback remoteCallback = callbacks.get(requestCode);
+ if (resultCode != RESULT_OK) {
+ Exception e = (Exception) data.getSerializableExtra(EXTRA_ERROR);
+ sendError(remoteCallback, e == null ? new Exception("Result was " + resultCode) : e);
+ return;
+ }
+ final Bundle result = new Bundle();
+ result.putParcelable(EXTRA_RETURN_RESULT, data.getParcelableExtra(EXTRA_RETURN_RESULT));
+ remoteCallback.sendResult(result);
+ finish();
+ }
+
+ private void bindService(RemoteCallback remoteCallback, String packageName) {
+ final String SERVICE_NAME = "android.appenumeration.testapp.DummyService";
+ final Intent intent = new Intent();
+ intent.setClassName(packageName, SERVICE_NAME);
+ final ServiceConnection serviceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ // No-op
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ // No-op
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ // Remote service die
+ finish();
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ // Since the DummyService doesn't implement onBind, it returns null and
+ // onNullBinding would be called. Use postDelayed to keep this service
+ // connection alive for 3 seconds.
+ mainHandler.postDelayed(() -> {
+ unbindService(this);
+ finish();
+ }, TIMEOUT_MS);
+ }
+ };
+
+ final boolean bound = bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
+ final Bundle result = new Bundle();
+ result.putBoolean(EXTRA_RETURN_RESULT, bound);
+ remoteCallback.sendResult(result);
+ // Don't invoke finish() right here if service is bound successfully to keep the service
+ // connection alive since the ServiceRecord would be remove from the ServiceMap once no
+ // client is binding the service.
+ if (!bound) finish();
+ }
+}
diff --git a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
index 0f1d10f..aab4138 100644
--- a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
+++ b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
@@ -18,8 +18,14 @@
import static android.Manifest.permission.SET_PREFERRED_APPLICATIONS;
import static android.appenumeration.cts.Constants.ACTION_AWAIT_LAUNCHER_APPS_CALLBACK;
+import static android.appenumeration.cts.Constants.ACTION_AWAIT_LAUNCHER_APPS_SESSION_CALLBACK;
import static android.appenumeration.cts.Constants.ACTION_BIND_SERVICE;
+import static android.appenumeration.cts.Constants.ACTION_CAN_PACKAGE_QUERY;
+import static android.appenumeration.cts.Constants.ACTION_CHECK_PACKAGE;
import static android.appenumeration.cts.Constants.ACTION_CHECK_SIGNATURES;
+import static android.appenumeration.cts.Constants.ACTION_CHECK_URI_PERMISSION;
+import static android.appenumeration.cts.Constants.ACTION_GET_ALL_PACKAGE_INSTALLER_SESSIONS;
+import static android.appenumeration.cts.Constants.ACTION_GET_ALL_SESSIONS;
import static android.appenumeration.cts.Constants.ACTION_GET_INSTALLED_ACCESSIBILITYSERVICES_PACKAGES;
import static android.appenumeration.cts.Constants.ACTION_GET_INSTALLED_APPWIDGET_PROVIDERS;
import static android.appenumeration.cts.Constants.ACTION_GET_INSTALLED_PACKAGES;
@@ -28,22 +34,32 @@
import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGES_FOR_UID;
import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGE_INFO;
import static android.appenumeration.cts.Constants.ACTION_GET_PREFERRED_ACTIVITIES;
+import static android.appenumeration.cts.Constants.ACTION_GET_SESSION_INFO;
import static android.appenumeration.cts.Constants.ACTION_GET_SHAREDLIBRARY_DEPENDENT_PACKAGES;
+import static android.appenumeration.cts.Constants.ACTION_GET_STAGED_SESSIONS;
import static android.appenumeration.cts.Constants.ACTION_GET_SYNCADAPTER_PACKAGES_FOR_AUTHORITY;
import static android.appenumeration.cts.Constants.ACTION_GET_SYNCADAPTER_TYPES;
+import static android.appenumeration.cts.Constants.ACTION_GRANT_URI_PERMISSION;
import static android.appenumeration.cts.Constants.ACTION_HAS_SIGNING_CERTIFICATE;
import static android.appenumeration.cts.Constants.ACTION_JUST_FINISH;
+import static android.appenumeration.cts.Constants.ACTION_LAUNCHER_APPS_GET_SUSPENDED_PACKAGE_LAUNCHER_EXTRAS;
import static android.appenumeration.cts.Constants.ACTION_LAUNCHER_APPS_IS_ACTIVITY_ENABLED;
+import static android.appenumeration.cts.Constants.ACTION_LAUNCHER_APPS_SHOULD_HIDE_FROM_SUGGESTIONS;
import static android.appenumeration.cts.Constants.ACTION_MANIFEST_ACTIVITY;
import static android.appenumeration.cts.Constants.ACTION_MANIFEST_PROVIDER;
import static android.appenumeration.cts.Constants.ACTION_MANIFEST_SERVICE;
+import static android.appenumeration.cts.Constants.ACTION_PENDING_INTENT_GET_ACTIVITY;
+import static android.appenumeration.cts.Constants.ACTION_PENDING_INTENT_GET_CREATOR_PACKAGE;
import static android.appenumeration.cts.Constants.ACTION_QUERY_ACTIVITIES;
import static android.appenumeration.cts.Constants.ACTION_QUERY_PROVIDERS;
import static android.appenumeration.cts.Constants.ACTION_QUERY_RESOLVER;
import static android.appenumeration.cts.Constants.ACTION_QUERY_SERVICES;
+import static android.appenumeration.cts.Constants.ACTION_REQUEST_SYNC_AND_AWAIT_STATUS;
+import static android.appenumeration.cts.Constants.ACTION_REVOKE_URI_PERMISSION;
import static android.appenumeration.cts.Constants.ACTION_SET_INSTALLER_PACKAGE_NAME;
import static android.appenumeration.cts.Constants.ACTION_START_DIRECTLY;
import static android.appenumeration.cts.Constants.ACTION_START_FOR_RESULT;
+import static android.appenumeration.cts.Constants.ACTION_TAKE_PERSISTABLE_URI_PERMISSION;
import static android.appenumeration.cts.Constants.ACTIVITY_CLASS_DUMMY_ACTIVITY;
import static android.appenumeration.cts.Constants.ACTIVITY_CLASS_TEST;
import static android.appenumeration.cts.Constants.CALLBACK_EVENT_INVALID;
@@ -52,11 +68,14 @@
import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_ADDED;
import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_CHANGED;
import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_REMOVED;
+import static android.appenumeration.cts.Constants.EXTRA_ACCOUNT;
import static android.appenumeration.cts.Constants.EXTRA_AUTHORITY;
import static android.appenumeration.cts.Constants.EXTRA_CERT;
import static android.appenumeration.cts.Constants.EXTRA_DATA;
import static android.appenumeration.cts.Constants.EXTRA_ERROR;
import static android.appenumeration.cts.Constants.EXTRA_FLAGS;
+import static android.appenumeration.cts.Constants.EXTRA_ID;
+import static android.appenumeration.cts.Constants.EXTRA_PENDING_INTENT;
import static android.appenumeration.cts.Constants.EXTRA_REMOTE_CALLBACK;
import static android.appenumeration.cts.Constants.EXTRA_REMOTE_READY_CALLBACK;
import static android.appenumeration.cts.Constants.QUERIES_ACTIVITY_ACTION;
@@ -64,7 +83,11 @@
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_PERM;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_PROVIDER;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_Q;
+import static android.appenumeration.cts.Constants.QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI;
+import static android.appenumeration.cts.Constants.QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI_APK;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_RECEIVES_PERM_URI;
+import static android.appenumeration.cts.Constants.QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI;
+import static android.appenumeration.cts.Constants.QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI_APK;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_RECEIVES_URI;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_SEES_INSTALLER;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_SEES_INSTALLER_APK;
@@ -72,6 +95,7 @@
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_USES_LIBRARY;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_USES_OPTIONAL_LIBRARY;
import static android.appenumeration.cts.Constants.QUERIES_PACKAGE;
+import static android.appenumeration.cts.Constants.QUERIES_PACKAGE_PROVIDER;
import static android.appenumeration.cts.Constants.QUERIES_PROVIDER_ACTION;
import static android.appenumeration.cts.Constants.QUERIES_PROVIDER_AUTH;
import static android.appenumeration.cts.Constants.QUERIES_SERVICE_ACTION;
@@ -98,6 +122,7 @@
import static android.appenumeration.cts.Constants.TARGET_FORCEQUERYABLE_NORMAL;
import static android.appenumeration.cts.Constants.TARGET_NO_API;
import static android.appenumeration.cts.Constants.TARGET_PREFERRED_ACTIVITY;
+import static android.appenumeration.cts.Constants.TARGET_PREFIX_WILDCARD_WEB;
import static android.appenumeration.cts.Constants.TARGET_SHARE;
import static android.appenumeration.cts.Constants.TARGET_SHARED_LIBRARY_PACKAGE;
import static android.appenumeration.cts.Constants.TARGET_SHARED_USER;
@@ -107,11 +132,13 @@
import static android.appenumeration.cts.Constants.TARGET_SYNCADAPTER_SHARED_USER;
import static android.appenumeration.cts.Constants.TARGET_WEB;
import static android.content.Intent.EXTRA_PACKAGES;
+import static android.content.Intent.EXTRA_UID;
import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.content.pm.PackageManager.SIGNATURE_MATCH;
import static android.content.pm.PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
import static android.os.Process.INVALID_UID;
+import static android.os.Process.ROOT_UID;
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
@@ -119,6 +146,7 @@
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.emptyArray;
+import static org.hamcrest.Matchers.emptyOrNullString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
@@ -132,13 +160,20 @@
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
+import android.Manifest;
+import android.accounts.Account;
+import android.accounts.AccountManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SyncAdapterType;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.content.res.Resources;
@@ -155,8 +190,12 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.compatibility.common.util.SystemUtil;
import com.android.compatibility.common.util.AmUtils;
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.LocalIntentSender;
+import com.android.cts.install.lib.TestApp;
import org.hamcrest.core.IsNull;
import org.junit.AfterClass;
@@ -177,6 +216,7 @@
import java.util.List;
import java.util.Objects;
import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
@@ -189,9 +229,17 @@
private static boolean sGlobalFeatureEnabled;
private static PackageManager sPm;
+ private static AccountManager sAccountManager;
// The shared library for getting dependent packages
private static final String TEST_SHARED_LIB_NAME = "android.test.runner";
+ private static final String TEST_NONEXISTENT_PACKAGE_NAME_1 = "com.android.cts.nonexistent1";
+ private static final String TEST_NONEXISTENT_PACKAGE_NAME_2 = "com.android.cts.nonexistent2";
+
+ private static final Account ACCOUNT_SYNCADAPTER = new Account(
+ TARGET_SYNCADAPTER, "android.appenumeration.account.type");
+ private static final Account ACCOUNT_SYNCADAPTER_SHARED_USER = new Account(
+ TARGET_SYNCADAPTER_SHARED_USER, "android.appenumeration.shareduid.account.type");
@Rule
public TestName name = new TestName();
@@ -216,12 +264,24 @@
sResponseHandler = new Handler(sResponseThread.getLooper());
sPm = InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
+ sAccountManager = AccountManager.get(
+ InstrumentationRegistry.getInstrumentation().getContext());
+
+ assertThat(sAccountManager.addAccountExplicitly(ACCOUNT_SYNCADAPTER,
+ null /* password */, null /* userdata */), is(true));
+ assertThat(sAccountManager.addAccountExplicitly(ACCOUNT_SYNCADAPTER_SHARED_USER,
+ null /* password */, null /* userdata */), is(true));
}
@AfterClass
public static void tearDown() {
if (!sGlobalFeatureEnabled) return;
sResponseThread.quit();
+
+ assertThat(sAccountManager.removeAccountExplicitly(ACCOUNT_SYNCADAPTER),
+ is(true));
+ assertThat(sAccountManager.removeAccountExplicitly(ACCOUNT_SYNCADAPTER_SHARED_USER),
+ is(true));
}
@Test
@@ -312,6 +372,45 @@
assertVisible(QUERIES_NOTHING_RECEIVES_PERM_URI, QUERIES_NOTHING_PERM);
}
+ @Test
+ public void startActivityWithUriGrant_cannotSeeProviderAfterUpdated() throws Exception {
+ assertNotVisible(QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI, QUERIES_NOTHING_PERM);
+
+ // send with uri grant flags; should be visible
+ startExplicitActivityWithIntent(QUERIES_NOTHING_PERM,
+ QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI,
+ new Intent(ACTION_JUST_FINISH)
+ .setData(Uri.parse("content://" + QUERIES_NOTHING_PERM + "3/test"))
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION));
+ assertVisible(QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI, QUERIES_NOTHING_PERM);
+
+ // update the package; shouldn't be visible
+ runShellCommand("pm install " + QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI_APK);
+ // Wait until the updating is done
+ AmUtils.waitForBroadcastIdle();
+ assertNotVisible(QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI, QUERIES_NOTHING_PERM);
+ }
+
+ @Test
+ public void startActivityWithPersistableUriGrant_canSeeProviderAfterUpdated() throws Exception {
+ assertNotVisible(QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI, QUERIES_NOTHING_PERM);
+
+ // send with persistable uri grant flags; should be visible
+ startExplicitActivityWithIntent(QUERIES_NOTHING_PERM,
+ QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI,
+ new Intent(ACTION_TAKE_PERSISTABLE_URI_PERMISSION)
+ .setData(Uri.parse("content://" + QUERIES_NOTHING_PERM + "3/test"))
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION));
+ assertVisible(QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI, QUERIES_NOTHING_PERM);
+
+ // update the package; should be still visible
+ runShellCommand("pm install " + QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI_APK);
+ // Wait until the updating is done
+ AmUtils.waitForBroadcastIdle();
+ assertVisible(QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI, QUERIES_NOTHING_PERM);
+ }
+
private void startExplicitActivityWithIntent(
String sourcePackageName, String targetPackageName, Intent intent) throws Exception {
sendCommandBlocking(sourcePackageName, targetPackageName,
@@ -639,6 +738,18 @@
assertVisible(QUERIES_WILDCARD_BROWSER, TARGET_BROWSER_WILDCARD);
}
+ @Test
+ public void queriesWildcardWeb_canSeePrefixWildcardWeb() throws Exception {
+ assertNotVisible(QUERIES_NOTHING, TARGET_PREFIX_WILDCARD_WEB);
+ assertVisible(QUERIES_WILDCARD_BROWSABLE, TARGET_PREFIX_WILDCARD_WEB);
+ assertVisible(QUERIES_WILDCARD_WEB, TARGET_PREFIX_WILDCARD_WEB);
+ }
+
+ @Test
+ public void queriesWildcardBrowser_cannotseePrefixWildcardWeb() throws Exception {
+ assertNotVisible(QUERIES_NOTHING, TARGET_PREFIX_WILDCARD_WEB);
+ assertNotVisible(QUERIES_WILDCARD_BROWSER, TARGET_PREFIX_WILDCARD_WEB);
+ }
@Test
public void queriesWildcardEditor() throws Exception {
@@ -761,6 +872,66 @@
}
@Test
+ public void uninstallTarget_broadcastFullyRemoved_notVisibleDoesNotReceive() throws Exception {
+ ensurePackageIsInstalled(TARGET_STUB, TARGET_STUB_APK);
+ final Result result = sendCommand(QUERIES_NOTHING, TARGET_STUB,
+ /* targetUid */ INVALID_UID, /* intentExtra */ null,
+ Constants.ACTION_AWAIT_PACKAGE_FULLY_REMOVED, /* waitForReady */ true);
+ runShellCommand("pm uninstall " + TARGET_STUB);
+ try {
+ result.await();
+ fail();
+ } catch (MissingBroadcastException e) {
+ // hooray
+ }
+ }
+
+ @Test
+ public void uninstallTarget_broadcastFullyRemoved_visibleReceives() throws Exception {
+ ensurePackageIsInstalled(TARGET_STUB, TARGET_STUB_APK);
+ final Result result = sendCommand(QUERIES_NOTHING_PERM, TARGET_STUB,
+ /* targetUid */ INVALID_UID, /* intentExtra */ null,
+ Constants.ACTION_AWAIT_PACKAGE_FULLY_REMOVED, /* waitForReady */ true);
+ runShellCommand("pm uninstall " + TARGET_STUB);
+ try {
+ Assert.assertEquals(TARGET_STUB,
+ Uri.parse(result.await().getString(EXTRA_DATA)).getSchemeSpecificPart());
+ } catch (MissingBroadcastException e) {
+ fail();
+ }
+ }
+
+ @Test
+ public void clearTargetData_broadcastDataCleared_notVisibleDoesNotReceive() throws Exception {
+ ensurePackageIsInstalled(TARGET_STUB, TARGET_STUB_APK);
+ final Result result = sendCommand(QUERIES_NOTHING, TARGET_STUB,
+ /* targetUid */ INVALID_UID, /* intentExtra */ null,
+ Constants.ACTION_AWAIT_PACKAGE_DATA_CLEARED, /* waitForReady */ true);
+ runShellCommand("pm clear --user cur " + TARGET_STUB);
+ try {
+ result.await();
+ fail();
+ } catch (MissingBroadcastException e) {
+ // hooray
+ }
+ }
+
+ @Test
+ public void clearTargetData_broadcastDataCleared_visibleReceives() throws Exception {
+ ensurePackageIsInstalled(TARGET_STUB, TARGET_STUB_APK);
+ final Result result = sendCommand(QUERIES_NOTHING_PERM, TARGET_STUB,
+ /* targetUid */ INVALID_UID, /* intentExtra */ null,
+ Constants.ACTION_AWAIT_PACKAGE_DATA_CLEARED, /* waitForReady */ true);
+ runShellCommand("pm clear --user cur " + TARGET_STUB);
+ try {
+ Assert.assertEquals(TARGET_STUB,
+ Uri.parse(result.await().getString(EXTRA_DATA)).getSchemeSpecificPart());
+ } catch (MissingBroadcastException e) {
+ fail();
+ }
+ }
+
+ @Test
public void broadcastSuspended_visibleReceives() throws Exception {
assertBroadcastSuspendedVisible(QUERIES_PACKAGE,
Arrays.asList(TARGET_NO_API, TARGET_SYNCADAPTER),
@@ -923,6 +1094,549 @@
}
@Test
+ public void launcherAppsSessionCallback_queriesNothing_cannotSeeSession() throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Result result = sendCommandAndWaitForLauncherAppsSessionCallback(
+ QUERIES_NOTHING, sessionId);
+ commitSession(sessionId);
+ final Bundle response = result.await();
+ assertThat(response.getInt(EXTRA_ID), equalTo(SessionInfo.INVALID_ID));
+ } finally {
+ runShellCommand("pm uninstall " + TestApp.A);
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void launcherAppsSessionCallback_queriesNothingHasPermission_canSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Result result = sendCommandAndWaitForLauncherAppsSessionCallback(
+ QUERIES_NOTHING_PERM, sessionId);
+ commitSession(sessionId);
+ final Bundle response = result.await();
+ assertThat(response.getInt(EXTRA_ID), equalTo(sessionId));
+ } finally {
+ runShellCommand("pm uninstall " + TestApp.A);
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void launcherAppsSessionCallback_queriesPackage_canSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Result result = sendCommandAndWaitForLauncherAppsSessionCallback(
+ QUERIES_PACKAGE, sessionId);
+ commitSession(sessionId);
+ final Bundle response = result.await();
+ assertThat(response.getInt(EXTRA_ID), equalTo(sessionId));
+ } finally {
+ runShellCommand("pm uninstall " + TestApp.A);
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void launcherAppsSessionCallback_queriesNothingTargetsQ_canSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Result result = sendCommandAndWaitForLauncherAppsSessionCallback(
+ QUERIES_NOTHING_Q, sessionId);
+ commitSession(sessionId);
+ final Bundle response = result.await();
+ assertThat(response.getInt(EXTRA_ID), equalTo(sessionId));
+ } finally {
+ runShellCommand("pm uninstall " + TestApp.A);
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void launcherAppsSessionCallback_sessionOwner_canSeeSession() throws Exception {
+ try {
+ adoptShellPermissions();
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ final int expectedSessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Context context = InstrumentationRegistry
+ .getInstrumentation()
+ .getContext();
+ final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+ final PackageInstaller.SessionCallback
+ sessionCallback = new PackageInstaller.SessionCallback() {
+
+ @Override
+ public void onCreated(int sessionId) {
+ // No-op
+ }
+
+ @Override
+ public void onBadgingChanged(int sessionId) {
+ // No-op
+ }
+
+ @Override
+ public void onActiveChanged(int sessionId, boolean active) {
+ // No-op
+ }
+
+ @Override
+ public void onProgressChanged(int sessionId, float progress) {
+ // No-op
+ }
+
+ @Override
+ public void onFinished(int sessionId, boolean success) {
+ if (sessionId != expectedSessionId) {
+ return;
+ }
+
+ launcherApps.unregisterPackageInstallerSessionCallback(this);
+ countDownLatch.countDown();
+ }
+ };
+
+ launcherApps.registerPackageInstallerSessionCallback(context.getMainExecutor(),
+ sessionCallback);
+
+ commitSession(expectedSessionId);
+ assertTrue(countDownLatch.await(5, TimeUnit.SECONDS));
+ } finally {
+ runShellCommand("pm uninstall " + TestApp.A);
+ dropShellPermissions();
+ }
+ }
+
+ private Result sendCommandAndWaitForLauncherAppsSessionCallback(String sourcePackageName,
+ int expectedSessionId) throws Exception {
+ final Bundle extra = new Bundle();
+ extra.putInt(EXTRA_ID, expectedSessionId);
+ final Result result = sendCommand(sourcePackageName, /* targetPackageName */ null,
+ /* targetUid */ INVALID_UID, extra, ACTION_AWAIT_LAUNCHER_APPS_SESSION_CALLBACK,
+ /* waitForReady */ true);
+ return result;
+ }
+
+ @Test
+ public void launcherApps_getAllPkgInstallerSessions_queriesNothing_cannotSeeSessions()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Integer[] sessionIds = getSessionInfos(ACTION_GET_ALL_PACKAGE_INSTALLER_SESSIONS,
+ QUERIES_NOTHING, SessionInfo.INVALID_ID);
+ assertSessionNotVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void launcherApps_getAllPkgInstallerSessions_queriesNothingHasPermission_canSeeSessions()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Integer[] sessionIds = getSessionInfos(ACTION_GET_ALL_PACKAGE_INSTALLER_SESSIONS,
+ QUERIES_NOTHING_PERM, SessionInfo.INVALID_ID);
+ assertSessionVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void launcherApps_getAllPkgInstallerSessions_queriesPackage_canSeeSessions()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Integer[] sessionIds = getSessionInfos(ACTION_GET_ALL_PACKAGE_INSTALLER_SESSIONS,
+ QUERIES_PACKAGE, SessionInfo.INVALID_ID);
+ assertSessionVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void launcherApps_getAllPkgInstallerSessions_queriesNothingTargetsQ_canSeeSessions()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Integer[] sessionIds = getSessionInfos(ACTION_GET_ALL_PACKAGE_INSTALLER_SESSIONS,
+ QUERIES_NOTHING_Q, SessionInfo.INVALID_ID);
+ assertSessionVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void launcherApps_getAllPkgInstallerSessions_sessionOwner_canSeeSessions()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final LauncherApps launcherApps = InstrumentationRegistry
+ .getInstrumentation()
+ .getContext()
+ .getSystemService(LauncherApps.class);
+ final Integer[] sessionIds = launcherApps.getAllPackageInstallerSessions().stream()
+ .map(i -> i.getSessionId())
+ .distinct()
+ .toArray(Integer[]::new);
+ assertSessionVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void packageInstaller_getSessionInfo_queriesNothing_cannotSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Integer[] sessionIds = getSessionInfos(ACTION_GET_SESSION_INFO,
+ QUERIES_NOTHING, sessionId);
+ assertSessionNotVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void packageInstaller_getSessionInfo_queriesNothingHasPermission_canSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Integer[] sessionIds = getSessionInfos(ACTION_GET_SESSION_INFO,
+ QUERIES_NOTHING_PERM, sessionId);
+ assertSessionVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void packageInstaller_getSessionInfo_queriesPackage_canSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Integer[] sessionIds = getSessionInfos(ACTION_GET_SESSION_INFO,
+ QUERIES_PACKAGE, sessionId);
+ assertSessionVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void packageInstaller_getSessionInfo_queriesNothingTargetsQ_canSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Integer[] sessionIds = getSessionInfos(ACTION_GET_SESSION_INFO,
+ QUERIES_NOTHING_Q, sessionId);
+ assertSessionVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void packageInstaller_getSessionInfo_sessionOwner_canSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final PackageInstaller installer = InstrumentationRegistry
+ .getInstrumentation()
+ .getContext()
+ .getPackageManager()
+ .getPackageInstaller();
+ final SessionInfo info = installer.getSessionInfo(sessionId);
+ assertThat(info, IsNull.notNullValue());
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void packageInstaller_getStagedSessions_queriesNothing_cannotSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A).setStaged()
+ .createSession();
+ final Integer[] sessionIds = getSessionInfos(ACTION_GET_STAGED_SESSIONS,
+ QUERIES_NOTHING, sessionId);
+ assertSessionNotVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void packageInstaller_getStagedSessions_queriesNothingHasPermission_canSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A).setStaged()
+ .createSession();
+ final Integer[] sessionIds = getSessionInfos(ACTION_GET_STAGED_SESSIONS,
+ QUERIES_NOTHING_PERM, sessionId);
+ assertSessionVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void packageInstaller_getStagedSessions_queriesPackage_canSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A).setStaged()
+ .createSession();
+ final Integer[] sessionIds = getSessionInfos(ACTION_GET_STAGED_SESSIONS,
+ QUERIES_PACKAGE, sessionId);
+ assertSessionVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void packageInstaller_getStagedSessions_queriesNothingTargetsQ_canSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A).setStaged()
+ .createSession();
+ final Integer[] sessionIds = getSessionInfos(ACTION_GET_STAGED_SESSIONS,
+ QUERIES_NOTHING_Q, sessionId);
+ assertSessionVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void packageInstaller_getStagedSessions_sessionOwner_canSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A).setStaged()
+ .createSession();
+ final PackageInstaller installer = InstrumentationRegistry
+ .getInstrumentation()
+ .getContext()
+ .getPackageManager()
+ .getPackageInstaller();
+ final Integer[] sessionIds = installer.getStagedSessions().stream()
+ .map(i -> i.getSessionId())
+ .distinct()
+ .toArray(Integer[]::new);
+ assertSessionVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void packageInstaller_getAllSessions_queriesNothing_cannotSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Integer[] sessionIds = getSessionInfos(ACTION_GET_ALL_SESSIONS,
+ QUERIES_NOTHING, sessionId);
+ assertSessionNotVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void packageInstaller_getAllSessions_queriesNothingHasPermission_canSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Integer[] sessionIds = getSessionInfos(ACTION_GET_ALL_SESSIONS,
+ QUERIES_NOTHING_PERM, sessionId);
+ assertSessionVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void packageInstaller_getAllSessions_queriesPackage_canSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Integer[] sessionIds = getSessionInfos(ACTION_GET_ALL_SESSIONS,
+ QUERIES_PACKAGE, sessionId);
+ assertSessionVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void packageInstaller_getAllSessions_queriesNothingTargetsQ_canSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final Integer[] sessionIds = getSessionInfos(ACTION_GET_ALL_SESSIONS,
+ QUERIES_NOTHING_Q, sessionId);
+ assertSessionVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ @Test
+ public void packageInstaller_getAllSessions_sessionOwner_canSeeSession()
+ throws Exception {
+ try {
+ adoptShellPermissions();
+ final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+ .createSession();
+ final PackageInstaller installer = InstrumentationRegistry
+ .getInstrumentation()
+ .getContext()
+ .getPackageManager()
+ .getPackageInstaller();
+ final Integer[] sessionIds = installer.getAllSessions().stream()
+ .map(i -> i.getSessionId())
+ .distinct()
+ .toArray(Integer[]::new);
+ assertSessionVisible(sessionIds, sessionId);
+ } finally {
+ cleanUpSessions();
+ dropShellPermissions();
+ }
+ }
+
+ private Integer[] getSessionInfos(String action, String sourcePackageName, int sessionId)
+ throws Exception {
+ final Bundle extraData = new Bundle();
+ extraData.putInt(EXTRA_ID, sessionId);
+ final Bundle response = sendCommandBlocking(sourcePackageName, /* targetPackageName */ null,
+ extraData, action);
+ final List<Parcelable> parcelables = response.getParcelableArrayList(
+ Intent.EXTRA_RETURN_RESULT);
+ return parcelables.stream()
+ .map(i -> (i == null ? SessionInfo.INVALID_ID : ((SessionInfo) i).getSessionId()))
+ .distinct()
+ .toArray(Integer[]::new);
+ }
+
+ private void assertSessionVisible(Integer[] sessionIds, int sessionId) {
+ if (!sGlobalFeatureEnabled) {
+ return;
+ }
+ assertThat(sessionIds, hasItemInArray(sessionId));
+ }
+
+ private void assertSessionNotVisible(Integer[] sessionIds, int sessionId) {
+ if (!sGlobalFeatureEnabled) {
+ return;
+ }
+ assertThat(sessionIds, not(hasItemInArray(sessionId)));
+ }
+
+ private static void commitSession(int sessionId) throws Exception {
+ final PackageInstaller.Session session =
+ InstallUtils.openPackageInstallerSession(sessionId);
+ final LocalIntentSender sender = new LocalIntentSender();
+ session.commit(sender.getIntentSender());
+ InstallUtils.assertStatusSuccess(sender.getResult());
+ if (session.isStaged()) {
+ InstallUtils.waitForSessionReady(sessionId);
+ }
+ }
+
+ private void adoptShellPermissions() {
+ InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES);
+ }
+
+ private void dropShellPermissions() {
+ InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ private void cleanUpSessions() {
+ InstallUtils.getPackageInstaller().getMySessions().forEach(info -> {
+ try {
+ InstallUtils.getPackageInstaller().abandonSession(info.getSessionId());
+ } catch (Exception ignore) {
+ }
+ });
+ }
+
+ @Test
public void queriesResolver_grantsVisibilityToProvider() throws Exception {
assertNotVisible(QUERIES_NOTHING_PROVIDER, QUERIES_NOTHING_PERM);
@@ -983,6 +1697,32 @@
}
@Test
+ public void queriesPackage_requestSync_canSeeSyncadapterTarget()
+ throws Exception {
+ assertThat(requestSyncAndAwaitStatus(QUERIES_PACKAGE,
+ ACCOUNT_SYNCADAPTER, TARGET_SYNCADAPTER),
+ is(true));
+ }
+
+ @Test
+ public void queriesNothingSharedUser_requestSync_canSeeSyncadapterSharedUserTarget()
+ throws Exception {
+ assertThat(requestSyncAndAwaitStatus(QUERIES_NOTHING_SHARED_USER,
+ ACCOUNT_SYNCADAPTER_SHARED_USER, TARGET_SYNCADAPTER_SHARED_USER),
+ is(true));
+ }
+
+ @Test
+ public void queriesNothing_requestSync_cannotSeeSyncadapterTarget() {
+ assertThrows(MissingCallbackException.class,
+ () -> requestSyncAndAwaitStatus(QUERIES_NOTHING,
+ ACCOUNT_SYNCADAPTER, TARGET_SYNCADAPTER));
+ assertThrows(MissingCallbackException.class,
+ () -> requestSyncAndAwaitStatus(QUERIES_NOTHING,
+ ACCOUNT_SYNCADAPTER_SHARED_USER, TARGET_SYNCADAPTER_SHARED_USER));
+ }
+
+ @Test
public void queriesNothingSharedUser_getSyncAdapterPackages_canSeeSyncadapterSharedUserTarget()
throws Exception {
assertVisible(QUERIES_NOTHING_SHARED_USER, TARGET_SYNCADAPTER_SHARED_USER,
@@ -1010,6 +1750,64 @@
}
@Test
+ public void launcherAppsGetSuspendedPackageLauncherExtras_queriesNothingHasPerm_canGetExtras()
+ throws Exception {
+ try {
+ setPackagesSuspendedWithLauncherExtras(/* suspend */ true,
+ Arrays.asList(TARGET_NO_API), /* extras */ true);
+ Assert.assertNotNull(launcherAppsGetSuspendedPackageLauncherExtras(QUERIES_NOTHING_PERM,
+ TARGET_NO_API));
+ } finally {
+ setPackagesSuspended(/* suspend */ false, Arrays.asList(TARGET_NO_API));
+ }
+ }
+
+ @Test
+ public void launcherAppsGetSuspendedPackageLauncherExtras_queriesNothing_cannotGetExtras()
+ throws Exception {
+ try {
+ setPackagesSuspendedWithLauncherExtras(/* suspend */ true,
+ Arrays.asList(TARGET_NO_API), /* extras */ true);
+ Assert.assertNull(launcherAppsGetSuspendedPackageLauncherExtras(QUERIES_NOTHING,
+ TARGET_NO_API));
+ } finally {
+ setPackagesSuspended(/* suspend */ false, Arrays.asList(TARGET_NO_API));
+ }
+ }
+
+ @Test
+ public void launcherAppsShouldHideFromSuggestions_queriesPackage_canSeeNoApi()
+ throws Exception {
+ setDistractingPackageRestrictions(new String[]{TARGET_NO_API},
+ PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS);
+
+ try {
+ final boolean hideFromSuggestions = shouldHideFromSuggestions(
+ QUERIES_PACKAGE, TARGET_NO_API);
+ assertThat(hideFromSuggestions, is(true));
+ } finally {
+ setDistractingPackageRestrictions(new String[]{TARGET_NO_API},
+ PackageManager.RESTRICTION_NONE);
+ }
+ }
+
+ @Test
+ public void launcherAppsShouldHideFromSuggestions_queriesNothing_cannotSeeNoApi()
+ throws Exception {
+ setDistractingPackageRestrictions(new String[]{TARGET_NO_API},
+ PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS);
+
+ try {
+ final boolean hideFromSuggestions = shouldHideFromSuggestions(
+ QUERIES_NOTHING, TARGET_NO_API);
+ assertThat(hideFromSuggestions, is(false));
+ } finally {
+ setDistractingPackageRestrictions(new String[]{TARGET_NO_API},
+ PackageManager.RESTRICTION_NONE);
+ }
+ }
+
+ @Test
public void queriesPackage_canSeeAppWidgetProviderTarget() throws Exception {
assumeTrue(InstrumentationRegistry.getInstrumentation().getContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS));
@@ -1097,6 +1895,130 @@
assertThat(ex.getMessage(), containsString(TARGET_NO_API));
}
+ @Test
+ public void queriesPackageHasProvider_checkUriPermission_canSeeNoApi() throws Exception {
+ final int permissionResult = checkUriPermission(QUERIES_PACKAGE_PROVIDER, TARGET_NO_API);
+ assertThat(permissionResult, is(PackageManager.PERMISSION_GRANTED));
+ }
+
+ @Test
+ public void queriesPackageHasProvider_checkUriPermission_cannotSeeFilters() throws Exception {
+ final int permissionResult = checkUriPermission(QUERIES_PACKAGE_PROVIDER, TARGET_FILTERS);
+ assertThat(permissionResult, is(PackageManager.PERMISSION_DENIED));
+ }
+
+ @Test
+ public void queriesPackageHasProvider_grantUriPermission_canSeeNoApi() throws Exception {
+ try {
+ grantUriPermission(QUERIES_PACKAGE_PROVIDER, TARGET_NO_API);
+ assertThat(InstrumentationRegistry.getInstrumentation().getContext()
+ .checkUriPermission(
+ Uri.parse("content://" + QUERIES_PACKAGE_PROVIDER),
+ 0 /* pid */,
+ sPm.getPackageUid(TARGET_NO_API, 0 /* flags */),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION),
+ is(PackageManager.PERMISSION_GRANTED));
+ } finally {
+ revokeUriPermission(QUERIES_PACKAGE_PROVIDER);
+ }
+ }
+
+ @Test
+ public void queriesPackageHasProvider_grantUriPermission_cannotSeeFilters() throws Exception {
+ try {
+ grantUriPermission(QUERIES_PACKAGE_PROVIDER, TARGET_FILTERS);
+ assertThat(InstrumentationRegistry.getInstrumentation().getContext()
+ .checkUriPermission(
+ Uri.parse("content://" + QUERIES_PACKAGE_PROVIDER),
+ 0 /* pid */,
+ sPm.getPackageUid(TARGET_FILTERS, 0 /* flags */),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION),
+ is(PackageManager.PERMISSION_DENIED));
+ } finally {
+ revokeUriPermission(QUERIES_PACKAGE_PROVIDER);
+ }
+ }
+
+ @Test
+ public void canPackageQuery_queriesActivityAction_canSeeFilters() throws Exception {
+ assertThat(sPm.canPackageQuery(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS),
+ is(true));
+ }
+
+ @Test
+ public void canPackageQuery_queriesNothing_cannotSeeFilters() throws Exception {
+ assertThat(sPm.canPackageQuery(QUERIES_NOTHING, TARGET_FILTERS),
+ is(false));
+ }
+
+ @Test
+ public void canPackageQuery_withNonexistentPackages() {
+ assertThrows(PackageManager.NameNotFoundException.class,
+ () -> sPm.canPackageQuery(
+ TEST_NONEXISTENT_PACKAGE_NAME_1, TEST_NONEXISTENT_PACKAGE_NAME_2));
+ assertThrows(PackageManager.NameNotFoundException.class,
+ () -> sPm.canPackageQuery(
+ QUERIES_NOTHING_PERM, TEST_NONEXISTENT_PACKAGE_NAME_2));
+ assertThrows(PackageManager.NameNotFoundException.class,
+ () -> sPm.canPackageQuery(
+ TEST_NONEXISTENT_PACKAGE_NAME_1, TARGET_FILTERS));
+ }
+
+ @Test
+ public void canPackageQuery_callerHasNoPackageVisibility() {
+ assertThrows(PackageManager.NameNotFoundException.class,
+ () -> canPackageQuery(
+ QUERIES_NOTHING, QUERIES_ACTIVITY_ACTION, TARGET_FILTERS));
+ assertThrows(PackageManager.NameNotFoundException.class,
+ () -> canPackageQuery(
+ QUERIES_NOTHING_SHARED_USER, QUERIES_PACKAGE, TARGET_SHARED_USER));
+ }
+
+ @Test
+ public void checkPackage_queriesNothing_validateFailed() {
+ // Using ROOT_UID here to pass the check in #verifyAndGetBypass, this is intended by design.
+ assertThrows(SecurityException.class,
+ () -> checkPackage(QUERIES_NOTHING, TARGET_FILTERS, ROOT_UID));
+ }
+
+ @Test
+ public void checkPackage_queriesNothing_targetIsNotExisting_validateFailed() {
+ // Using ROOT_UID here to pass the check in #verifyAndGetBypass, this is intended by design.
+ assertThrows(SecurityException.class,
+ () -> checkPackage(QUERIES_NOTHING, TEST_NONEXISTENT_PACKAGE_NAME_1, ROOT_UID));
+ }
+
+ @Test
+ public void checkPackage_queriesNothingHasPerm_validateSuccessful() throws Exception {
+ // Using ROOT_UID here to pass the check in #verifyAndGetBypass, this is intended by design.
+ assertThat(checkPackage(QUERIES_NOTHING_PERM, TARGET_FILTERS, ROOT_UID), is(true));
+ }
+
+ @Test
+ public void checkPackage_queriesNothingHasPerm_targetIsNotExisting_validateFailed()
+ throws Exception {
+ // Using ROOT_UID here to pass the check in #verifyAndGetBypass, this is intended by design.
+ assertThrows(SecurityException.class,
+ () -> checkPackage(QUERIES_NOTHING_PERM, TEST_NONEXISTENT_PACKAGE_NAME_1,
+ ROOT_UID));
+ }
+
+ @Test
+ public void pendingIntent_getCreatorPackage_queriesPackage_canSeeNoApi()
+ throws Exception {
+ final PendingIntent pendingIntent = getPendingIntentActivity(TARGET_NO_API);
+ assertThat(getPendingIntentCreatorPackage(QUERIES_PACKAGE, pendingIntent),
+ is(TARGET_NO_API));
+ }
+
+ @Test
+ public void pendingIntent_getCreatorPackage_queriesNothing_cannotSeeNoApi()
+ throws Exception {
+ final PendingIntent pendingIntent = getPendingIntentActivity(TARGET_NO_API);
+ assertThat(getPendingIntentCreatorPackage(QUERIES_NOTHING, pendingIntent),
+ is(emptyOrNullString()));
+ }
+
private void assertNotVisible(String sourcePackageName, String targetPackageName)
throws Exception {
if (!sGlobalFeatureEnabled) return;
@@ -1270,6 +2192,15 @@
return certs;
}
+ private boolean checkPackage(String sourcePackageName, String targetPackageName, int targetUid)
+ throws Exception {
+ final Bundle extra = new Bundle();
+ extra.putInt(EXTRA_UID, targetUid);
+ final Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName, extra,
+ ACTION_CHECK_PACKAGE);
+ return response.getBoolean(Intent.EXTRA_RETURN_RESULT);
+ }
+
private PackageInfo startForResult(String sourcePackageName, String targetPackageName)
throws Exception {
Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
@@ -1283,7 +2214,7 @@
InstrumentationRegistry.getInstrumentation().getContext(), 100,
new Intent("android.appenumeration.cts.action.SEND_RESULT").setComponent(
new ComponentName(targetPackageName,
- "android.appenumeration.cts.query.TestActivity")),
+ "android.appenumeration.cts.TestActivity")),
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
@@ -1377,7 +2308,22 @@
return response.getStringArray(Intent.EXTRA_PACKAGES);
}
+ private boolean requestSyncAndAwaitStatus(String sourcePackageName, Account account,
+ String targetPackageName) throws Exception {
+ final Bundle extraData = new Bundle();
+ extraData.putParcelable(EXTRA_ACCOUNT, account);
+ extraData.putString(EXTRA_AUTHORITY, targetPackageName + ".authority");
+ final Bundle response = sendCommandBlocking(sourcePackageName, /* targetPackageName */ null,
+ extraData, ACTION_REQUEST_SYNC_AND_AWAIT_STATUS);
+ return response.getBoolean(Intent.EXTRA_RETURN_RESULT);
+ }
+
private void setPackagesSuspended(boolean suspend, List<String> packages) {
+ setPackagesSuspendedWithLauncherExtras(suspend, packages, /* persistableBundle */ false);
+ }
+
+ private void setPackagesSuspendedWithLauncherExtras(boolean suspend, List<String> packages,
+ boolean extras) {
final StringBuilder cmd = new StringBuilder("pm ");
if (suspend) {
cmd.append("suspend");
@@ -1385,6 +2331,9 @@
cmd.append("unsuspend");
}
cmd.append(" --user cur");
+ if (extras) {
+ cmd.append(" --les foo bar");
+ }
packages.stream().forEach(p -> cmd.append(" ").append(p));
runShellCommand(cmd.toString());
}
@@ -1398,6 +2347,13 @@
return response.getBoolean(Intent.EXTRA_RETURN_RESULT);
}
+ private Bundle launcherAppsGetSuspendedPackageLauncherExtras(String sourcePackageName,
+ String targetPackageName) throws Exception {
+ final Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
+ /* extraData */ null, ACTION_LAUNCHER_APPS_GET_SUSPENDED_PACKAGE_LAUNCHER_EXTRAS);
+ return response.getBundle(Intent.EXTRA_RETURN_RESULT);
+ }
+
private String[] getSharedLibraryDependentPackages(String sourcePackageName) throws Exception {
final Bundle extraData = new Bundle();
final Bundle response = sendCommandBlocking(sourcePackageName, TEST_SHARED_LIB_NAME,
@@ -1420,6 +2376,70 @@
extraData, ACTION_SET_INSTALLER_PACKAGE_NAME);
}
+ private void setDistractingPackageRestrictions(String[] packagesToRestrict,
+ int distractionFlags) throws Exception {
+ final String[] failed = SystemUtil.callWithShellPermissionIdentity(
+ () -> sPm.setDistractingPackageRestrictions(packagesToRestrict, distractionFlags));
+ assertThat(failed, emptyArray());
+ }
+
+ private boolean shouldHideFromSuggestions(String sourcePackageName, String targetPackageName)
+ throws Exception {
+ final Bundle extraData = new Bundle();
+ extraData.putInt(Intent.EXTRA_USER, Process.myUserHandle().getIdentifier());
+ final Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName, extraData,
+ ACTION_LAUNCHER_APPS_SHOULD_HIDE_FROM_SUGGESTIONS);
+ return response.getBoolean(Intent.EXTRA_RETURN_RESULT);
+ }
+
+ private int checkUriPermission(String sourcePackageName, String targetPackageName)
+ throws Exception {
+ final int targetUid = sPm.getPackageUid(targetPackageName, /* flags */ 0);
+ final Bundle extraData = new Bundle();
+ extraData.putString(EXTRA_AUTHORITY, sourcePackageName);
+ final Result result = sendCommand(sourcePackageName, targetPackageName, targetUid,
+ extraData, ACTION_CHECK_URI_PERMISSION, /* waitForReady */ false);
+ final Bundle response = result.await();
+ return response.getInt(Intent.EXTRA_RETURN_RESULT);
+ }
+
+ private void grantUriPermission(String providerPackageName, String targetPackageName)
+ throws Exception {
+ final Bundle extraData = new Bundle();
+ extraData.putString(EXTRA_AUTHORITY, providerPackageName);
+ sendCommandBlocking(providerPackageName, targetPackageName, extraData,
+ ACTION_GRANT_URI_PERMISSION);
+ }
+
+ private void revokeUriPermission(String providerPackageName) throws Exception {
+ final Bundle extraData = new Bundle();
+ extraData.putString(EXTRA_AUTHORITY, providerPackageName);
+ sendCommandBlocking(providerPackageName, null /* targetPackageName */, extraData,
+ ACTION_REVOKE_URI_PERMISSION);
+ }
+
+ private boolean canPackageQuery(String callerPackageName, String sourcePackageName,
+ String targetPackageName) throws Exception {
+ final Bundle extraData = new Bundle();
+ extraData.putString(Intent.EXTRA_PACKAGE_NAME, targetPackageName);
+ final Bundle response = sendCommandBlocking(callerPackageName, sourcePackageName,
+ extraData, ACTION_CAN_PACKAGE_QUERY);
+ return response.getBoolean(Intent.EXTRA_RETURN_RESULT);
+ }
+
+ private PendingIntent getPendingIntentActivity(String sourcePackageName) throws Exception {
+ final Bundle bundle = sendCommandBlocking(sourcePackageName, null /* targetPackageName */,
+ null /* intentExtra */, ACTION_PENDING_INTENT_GET_ACTIVITY);
+ return bundle.getParcelable(EXTRA_PENDING_INTENT);
+ }
+
+ private String getPendingIntentCreatorPackage(String sourcePackageName,
+ PendingIntent pendingIntent) throws Exception {
+ final Bundle bundle = sendCommandBlocking(sourcePackageName, null /* targetPackageName */,
+ pendingIntent, ACTION_PENDING_INTENT_GET_CREATOR_PACKAGE);
+ return bundle.getString(Intent.EXTRA_PACKAGE_NAME);
+ }
+
interface Result {
Bundle await() throws Exception;
}
@@ -1443,7 +2463,7 @@
if (intentExtra instanceof Intent) {
intent.putExtra(Intent.EXTRA_INTENT, intentExtra);
} else if (intentExtra instanceof PendingIntent) {
- intent.putExtra("pendingIntent", intentExtra);
+ intent.putExtra(EXTRA_PENDING_INTENT, intentExtra);
} else if (intentExtra instanceof Bundle) {
intent.putExtra(EXTRA_DATA, intentExtra);
}
diff --git a/tests/tests/appop/AndroidTest.xml b/tests/tests/appop/AndroidTest.xml
index f817422..b25d2b8 100644
--- a/tests/tests/appop/AndroidTest.xml
+++ b/tests/tests/appop/AndroidTest.xml
@@ -17,6 +17,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
diff --git a/tests/tests/appop2/AndroidTest.xml b/tests/tests/appop2/AndroidTest.xml
index fc10a83..aa3b23f 100644
--- a/tests/tests/appop2/AndroidTest.xml
+++ b/tests/tests/appop2/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/batterysaving/src/android/os/cts/deviceidle/DeviceIdleTest.java b/tests/tests/batterysaving/src/android/os/cts/deviceidle/DeviceIdleTest.java
index 20410e7..6d96eca 100644
--- a/tests/tests/batterysaving/src/android/os/cts/deviceidle/DeviceIdleTest.java
+++ b/tests/tests/batterysaving/src/android/os/cts/deviceidle/DeviceIdleTest.java
@@ -15,23 +15,48 @@
*/
package android.os.cts.deviceidle;
+import static com.android.compatibility.common.util.TestUtils.waitUntil;
+
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import android.annotation.NonNull;
import android.content.Context;
+import android.content.IntentFilter;
import android.os.PowerManager;
+import android.support.test.uiautomator.UiDevice;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.CallbackAsserter;
+
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DeviceIdleTest {
+
+ private UiDevice mUiDevice;
+
+ @Before
+ public void setUp() {
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ BatteryUtils.runDumpsysBatteryReset();
+ }
+
@Test
public void testDeviceIdleManager() {
Context context = InstrumentationRegistry.getInstrumentation().getContext();
@@ -39,6 +64,62 @@
}
@Test
+ public void testLightIdleMode() throws Exception {
+ final String output = mUiDevice.executeShellCommand("cmd deviceidle enabled light").trim();
+ final boolean isEnabled = Integer.parseInt(output) != 0;
+ assumeTrue("device idle not enabled", isEnabled);
+
+ // Reset idle state.
+ setScreenState(true);
+ BatteryUtils.runDumpsysBatteryUnplug();
+ setScreenState(false);
+ PowerManager powerManager = InstrumentationRegistry.getInstrumentation().getContext()
+ .getSystemService(PowerManager.class);
+ waitUntil("Never made it to IDLE state", 30 /* seconds */, () -> {
+ final CallbackAsserter idleModeChangedBroadcastAsserter = CallbackAsserter.forBroadcast(
+ new IntentFilter(PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+ stepIdleState("light");
+ final String state = getIdleState("light");
+ if ("IDLE".equals(state)) {
+ idleModeChangedBroadcastAsserter.assertCalled(
+ "Didn't get light idle mode changed broadcast", 15 /* 15 seconds */);
+ assertTrue(powerManager.isDeviceLightIdleMode());
+ return true;
+ } else {
+ assertFalse("Returned true even though state is " + state,
+ powerManager.isDeviceLightIdleMode());
+ return false;
+ }
+ });
+
+ // We're IDLE. Step to IDLE_MAINTENANCE and confirm false is returned.
+ CallbackAsserter idleModeChangedBroadcastAsserter = CallbackAsserter.forBroadcast(
+ new IntentFilter(PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+ stepIdleState("light");
+ idleModeChangedBroadcastAsserter.assertCalled(
+ "Didn't get light idle mode changed broadcast", 15 /* 15 seconds */);
+ String state = getIdleState("light");
+ assertEquals("IDLE_MAINTENANCE", state);
+ assertFalse(powerManager.isDeviceLightIdleMode());
+
+ idleModeChangedBroadcastAsserter = CallbackAsserter.forBroadcast(
+ new IntentFilter(PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+ stepIdleState("light");
+ idleModeChangedBroadcastAsserter.assertCalled(
+ "Didn't get light idle mode changed broadcast", 15 /* 15 seconds */);
+ state = getIdleState("light");
+ assertEquals("IDLE", state);
+ assertTrue(powerManager.isDeviceLightIdleMode());
+
+ idleModeChangedBroadcastAsserter = CallbackAsserter.forBroadcast(
+ new IntentFilter(PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+ setScreenState(true);
+ idleModeChangedBroadcastAsserter.assertCalled(
+ "Didn't get light idle mode changed broadcast", 15 /* 15 seconds */);
+ assertFalse(powerManager.isDeviceLightIdleMode());
+ }
+
+ @Test
public void testPowerManagerIgnoringBatteryOptimizations() {
Context context = InstrumentationRegistry.getInstrumentation().getContext();
@@ -48,4 +129,26 @@
.isIgnoringBatteryOptimizations("no.such.package.!!!"));
}
+ @NonNull
+ private String getIdleState(String level) throws Exception {
+ return mUiDevice.executeShellCommand("cmd deviceidle get " + level).trim();
+ }
+
+ private void stepIdleState(String level) throws Exception {
+ mUiDevice.executeShellCommand("cmd deviceidle step " + level);
+ }
+
+ /**
+ * Set the screen state.
+ */
+ private void setScreenState(boolean on) throws Exception {
+ if (on) {
+ mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ mUiDevice.executeShellCommand("wm dismiss-keyguard");
+ } else {
+ mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
+ }
+ // Wait a little bit to make sure the screen state has changed.
+ Thread.sleep(2_000);
+ }
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BasicAdapterTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BasicAdapterTest.java
index 43183fe..d30e334 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BasicAdapterTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BasicAdapterTest.java
@@ -259,6 +259,18 @@
socket.close();
}
+ public void test_discoverableTimeout() {
+ if (!mHasBluetooth) {
+ // Skip the test if bluetooth is not present.
+ return;
+ }
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ assertTrue(BTAdapterUtils.disableAdapter(adapter, mContext));
+ assertEquals(null, adapter.getDiscoverableTimeout());
+ assertTrue(BTAdapterUtils.enableAdapter(adapter, mContext));
+ assertEquals(120, adapter.getDiscoverableTimeout().toSeconds());
+ }
+
private static void sleep(long t) {
try {
Thread.sleep(t);
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothClassTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothClassTest.java
index 3d407df..42b1ecc 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothClassTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothClassTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,7 +34,8 @@
@Override
protected void setUp() {
- mBluetoothClassHeadphones = new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
+ mBluetoothClassHeadphones =
+ new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
mBluetoothClassPhone = new BluetoothClass(BluetoothClass.Device.Major.PHONE);
mBluetoothClassService = new BluetoothClass(BluetoothClass.Service.NETWORKING);
}
@@ -47,19 +48,27 @@
@SmallTest
public void testGetMajorDeviceClass() {
- assertEquals(mBluetoothClassHeadphones.getMajorDeviceClass(), BluetoothClass.Device.Major.AUDIO_VIDEO);
+ assertEquals(
+ mBluetoothClassHeadphones.getMajorDeviceClass(),
+ BluetoothClass.Device.Major.AUDIO_VIDEO);
assertEquals(mBluetoothClassPhone.getMajorDeviceClass(), BluetoothClass.Device.Major.PHONE);
}
@SmallTest
public void testGetDeviceClass() {
- assertEquals(mBluetoothClassHeadphones.getDeviceClass(), BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
- assertEquals(mBluetoothClassPhone.getDeviceClass(), BluetoothClass.Device.PHONE_UNCATEGORIZED);
+ assertEquals(
+ mBluetoothClassHeadphones.getDeviceClass(),
+ BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
+ assertEquals(
+ mBluetoothClassPhone.getDeviceClass(),
+ BluetoothClass.Device.PHONE_UNCATEGORIZED);
}
@SmallTest
public void testGetClassOfDevice() {
- assertEquals(mBluetoothClassHeadphones.getClassOfDevice(), BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
+ assertEquals(
+ mBluetoothClassHeadphones.getClassOfDevice(),
+ BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
assertEquals(mBluetoothClassPhone.getClassOfDevice(), BluetoothClass.Device.Major.PHONE);
}
@@ -74,4 +83,13 @@
assertTrue(mBluetoothClassService.doesClassMatch(BluetoothClass.PROFILE_PANU));
assertFalse(mBluetoothClassService.doesClassMatch(BluetoothClass.PROFILE_OPP));
}
+
+ @SmallTest
+ public void testInnerClasses() {
+ // Just instantiate static inner classes for exposing constants
+ // to make test coverage tool happy.
+ BluetoothClass.Device device = new BluetoothClass.Device();
+ BluetoothClass.Device.Major major = new BluetoothClass.Device.Major();
+ BluetoothClass.Service service = new BluetoothClass.Service();
+ }
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
index 7f1a487..f5a99ce 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
@@ -85,7 +85,8 @@
"cmd companiondevice associate %d %s %s", userId, packageName, deviceAddress));
String output = runShellCommand("dumpsys companiondevice");
assertTrue("Package name missing from output", output.contains(packageName));
- assertTrue("Device address missing from output", output.contains(deviceAddress));
+ assertTrue("Device address missing from output",
+ output.toLowerCase().contains(deviceAddress.toLowerCase()));
// Takes time to update the CDM cache, so sleep to ensure the association is cached
try {
diff --git a/tests/tests/car/PermissionReadCarDisplayUnits/src/android/car/cts/permissionreadcardisplayunits/PermissionReadCarDisplayUnitsTest.java b/tests/tests/car/PermissionReadCarDisplayUnits/src/android/car/cts/permissionreadcardisplayunits/PermissionReadCarDisplayUnitsTest.java
index 54c68e7..23c549f 100644
--- a/tests/tests/car/PermissionReadCarDisplayUnits/src/android/car/cts/permissionreadcardisplayunits/PermissionReadCarDisplayUnitsTest.java
+++ b/tests/tests/car/PermissionReadCarDisplayUnits/src/android/car/cts/permissionreadcardisplayunits/PermissionReadCarDisplayUnitsTest.java
@@ -45,7 +45,7 @@
VehiclePropertyIds.FUEL_VOLUME_DISPLAY_UNITS,
VehiclePropertyIds.TIRE_PRESSURE_DISPLAY_UNITS,
VehiclePropertyIds.EV_BATTERY_DISPLAY_UNITS,
- /*VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS=*/ 289408516,
+ VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS,
VehiclePropertyIds.FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME)
.build();
@@ -69,4 +69,4 @@
PERMISSION_READ_CAR_DISPLAY_UNITS_PROPERTIES);
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/tests/car/src/android/car/cts/CarApiTestBase.java b/tests/tests/car/src/android/car/cts/CarApiTestBase.java
index 771978c..7cb3c89 100644
--- a/tests/tests/car/src/android/car/cts/CarApiTestBase.java
+++ b/tests/tests/car/src/android/car/cts/CarApiTestBase.java
@@ -78,6 +78,10 @@
}
}
+ public void startUser(int userId) throws Exception {
+ executeShellCommand("am start-user %d", userId);
+ }
+
protected synchronized Car getCar() {
return mCar;
}
diff --git a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
index ac17aac..7276419 100644
--- a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
@@ -29,6 +29,7 @@
import android.car.VehicleAreaType;
import android.car.VehicleAreaWheel;
import android.car.VehicleGear;
+import android.car.VehicleIgnitionState;
import android.car.VehiclePropertyIds;
import android.car.cts.utils.VehiclePropertyVerifier;
import android.car.hardware.CarPropertyConfig;
@@ -625,13 +626,10 @@
assertWithMessage(
"IGNITION_STATE must be a defined ignition state: "
+ ignitionState).that(
- ignitionState).isIn(ImmutableSet.of(
- /*VehicleIgnitionState.UNDEFINED=*/0,
- /*VehicleIgnitionState.LOCK=*/1,
- /*VehicleIgnitionState.OFF=*/2,
- /*VehicleIgnitionState.ACC=*/3,
- /*VehicleIgnitionState.ON=*/4,
- /*VehicleIgnitionState.START=*/5));
+ ignitionState).isIn(ImmutableSet.of(VehicleIgnitionState.UNDEFINED,
+ VehicleIgnitionState.LOCK, VehicleIgnitionState.OFF,
+ VehicleIgnitionState.ACC, VehicleIgnitionState.ON,
+ VehicleIgnitionState.START));
}).build().verify(mCarPropertyManager);
}
@@ -681,8 +679,7 @@
@Test
public void testVehicleSpeedDisplayUnitsIfSupported() {
- VehiclePropertyVerifier.newBuilder(/*VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS=*/
- 289408516,
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS,
CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
diff --git a/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java b/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java
new file mode 100644
index 0000000..3faf7c9
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.fail;
+
+import android.car.Car;
+import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleEvent;
+import android.car.user.CarUserManager.UserLifecycleListener;
+import android.os.NewUserRequest;
+import android.os.NewUserResponse;
+import android.os.SystemClock;
+import android.os.UserManager;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class CarServiceHelperServiceUpdatableTest extends CarApiTestBase {
+
+ private static final String TAG = CarServiceHelperServiceUpdatableTest.class.getSimpleName();
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Test
+ public void testSendUserLifecycleEvent() throws Exception {
+ // Add listener to check if user started
+ CarUserManager carUserManager = (CarUserManager) getCar()
+ .getCarManager(Car.CAR_USER_SERVICE);
+ LifecycleListener listener = new LifecycleListener();
+ carUserManager.addListener(Runnable::run, listener);
+
+ NewUserResponse response = null;
+ UserManager userManager = null;
+ try {
+ // get create User permissions
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(android.Manifest.permission.CREATE_USERS);
+
+ // CreateUser
+ userManager = mContext.getSystemService(UserManager.class);
+ response = userManager.createUser(new NewUserRequest.Builder().build());
+ assertThat(response.isSuccessful()).isTrue();
+
+ int userId = response.getUser().getIdentifier();
+ startUser(userId);
+ listener.assertEventReceived(userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING,
+ /* timeoutMs= */ 60_000);
+ } finally {
+ userManager.removeUser(response.getUser());
+ carUserManager.removeListener(listener);
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+ }
+
+ // TODO(214100537): Improve listener by removing sleep.
+ private final class LifecycleListener implements UserLifecycleListener {
+
+ private final List<UserLifecycleEvent> mEvents =
+ new ArrayList<CarUserManager.UserLifecycleEvent>();
+
+ private final Object mLock = new Object();
+
+ @Override
+ public void onEvent(UserLifecycleEvent event) {
+ Log.d(TAG, "Event received: " + event);
+ synchronized (mLock) {
+ mEvents.add(event);
+ }
+ }
+
+ public void assertEventReceived(int userId, int eventType, int timeoutMs)
+ throws InterruptedException {
+ long startTime = SystemClock.elapsedRealtime();
+ while (SystemClock.elapsedRealtime() - startTime < timeoutMs) {
+ boolean result = checkEvent(userId, eventType);
+ if (result) return;
+ Thread.sleep(1000);
+ }
+
+ fail("Event" + eventType + " was not received within timeoutMs: " + timeoutMs);
+ }
+
+ private boolean checkEvent(int userId, int eventType) {
+ synchronized (mLock) {
+ for (int i = 0; i < mEvents.size(); i++) {
+ if (mEvents.get(i).getUserHandle().getIdentifier() == userId
+ && mEvents.get(i).getEventType() == eventType) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/tests/tests/car/src/android/car/cts/CarTest.java b/tests/tests/car/src/android/car/cts/CarTest.java
index 8527189..3c9fff5 100644
--- a/tests/tests/car/src/android/car/cts/CarTest.java
+++ b/tests/tests/car/src/android/car/cts/CarTest.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
+import android.os.Build;
import android.os.IBinder;
import android.platform.test.annotations.AppModeFull;
import android.test.suitebuilder.annotation.SmallTest;
@@ -118,6 +119,31 @@
listenerImpl.waitForReady(DEFAULT_WAIT_TIMEOUT_MS);
}
+ @Test
+ public void testApiVersion() throws Exception {
+ int ApiVersionTooHigh = 1000000;
+ int MinorApiVersionTooHigh = 1000000;
+ assertThat(Car.isApiVersionAtLeast(Car.API_VERSION_MAJOR_INT)).isTrue();
+ assertThat(Car.isApiVersionAtLeast(ApiVersionTooHigh)).isFalse();
+
+ assertThat(Car.isApiVersionAtLeast(Car.API_VERSION_MAJOR_INT -1 ,
+ MinorApiVersionTooHigh)).isTrue();
+ assertThat(Car.isApiVersionAtLeast(Car.API_VERSION_MAJOR_INT,
+ Car.API_VERSION_MINOR_INT)).isTrue();
+ assertThat(Car.isApiVersionAtLeast(Car.API_VERSION_MAJOR_INT,
+ MinorApiVersionTooHigh)).isFalse();
+ assertThat(Car.isApiVersionAtLeast(ApiVersionTooHigh, 0)).isFalse();
+
+ assertThat(Car.isApiAndPlatformVersionAtLeast(Car.API_VERSION_MAJOR_INT,
+ Build.VERSION.SDK_INT)).isTrue();
+ assertThat(Car.isApiAndPlatformVersionAtLeast(Car.API_VERSION_MAJOR_INT,
+ Build.VERSION.SDK_INT + 1)).isFalse();
+ assertThat(Car.isApiAndPlatformVersionAtLeast(Car.API_VERSION_MAJOR_INT,
+ Car.API_VERSION_MINOR_INT, Build.VERSION.SDK_INT)).isTrue();
+ assertThat(Car.isApiAndPlatformVersionAtLeast(Car.API_VERSION_MAJOR_INT,
+ Car.API_VERSION_MINOR_INT, Build.VERSION.SDK_INT + 1)).isFalse();
+ }
+
private static void assertConnectedCar(Car car) {
assertThat(car).isNotNull();
assertThat(car.isConnected()).isTrue();
diff --git a/tests/tests/car/src/android/car/cts/EvChargingConnectorTypeTest.java b/tests/tests/car/src/android/car/cts/EvChargingConnectorTypeTest.java
new file mode 100644
index 0000000..ac009e0
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/EvChargingConnectorTypeTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.VehiclePropertyIds;
+import android.car.cts.utils.VehiclePropertyUtils;
+import android.car.hardware.property.EvChargingConnectorType;
+
+import org.junit.Test;
+
+import java.util.List;
+
+
+public class EvChargingConnectorTypeTest {
+
+ /**
+ * Test for {@link EvChargingConnectorType#toString()}
+ */
+ @Test
+ public void testToString() {
+ assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.UNKNOWN))
+ .isEqualTo("UNKNOWN");
+ assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.IEC_TYPE_1_AC))
+ .isEqualTo("IEC_TYPE_1_AC");
+ assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.IEC_TYPE_2_AC))
+ .isEqualTo("IEC_TYPE_2_AC");
+ assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.IEC_TYPE_3_AC))
+ .isEqualTo("IEC_TYPE_3_AC");
+ assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.IEC_TYPE_4_DC))
+ .isEqualTo("IEC_TYPE_4_DC");
+ assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.IEC_TYPE_1_CCS_DC))
+ .isEqualTo("IEC_TYPE_1_CCS_DC");
+ assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.IEC_TYPE_2_CCS_DC))
+ .isEqualTo("IEC_TYPE_2_CCS_DC");
+ assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.TESLA_HPWC))
+ .isEqualTo("TESLA_HPWC");
+ assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.TESLA_ROADSTER))
+ .isEqualTo("TESLA_ROADSTER");
+ assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.TESLA_SUPERCHARGER))
+ .isEqualTo("TESLA_SUPERCHARGER");
+ assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.GBT_AC))
+ .isEqualTo("GBT_AC");
+ assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.GBT_DC))
+ .isEqualTo("GBT_DC");
+ assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.OTHER))
+ .isEqualTo("OTHER");
+ assertThat(EvChargingConnectorType.toString(0x999)).isEqualTo("0x999");
+ }
+
+ /**
+ * Test if all system properties have a mapped string value.
+ */
+ @Test
+ public void testAllConnectorTypesAreMappedInToString() {
+ List<Integer> connectorTypes =
+ VehiclePropertyUtils.getIntegersFromDataEnums(EvChargingConnectorType.class);
+ for (int connectorType : connectorTypes) {
+ String propertyString = EvChargingConnectorType.toString(connectorType);
+ assertThat(propertyString.startsWith("0x")).isFalse();
+ }
+ }
+}
diff --git a/tests/tests/car/src/android/car/cts/VehicleGearTest.java b/tests/tests/car/src/android/car/cts/VehicleGearTest.java
index 5601757..319c7ed 100644
--- a/tests/tests/car/src/android/car/cts/VehicleGearTest.java
+++ b/tests/tests/car/src/android/car/cts/VehicleGearTest.java
@@ -19,13 +19,10 @@
import static com.google.common.truth.Truth.assertThat;
import android.car.VehicleGear;
-import android.car.VehiclePropertyIds;
-import android.util.Log;
+import android.car.cts.utils.VehiclePropertyUtils;
import org.junit.Test;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
import java.util.List;
public class VehicleGearTest {
@@ -60,25 +57,10 @@
*/
@Test
public void testAllGearsAreMappedInToString() {
- List<Integer> gears = getIntegersFromDataEnums(VehicleGear.class);
+ List<Integer> gears = VehiclePropertyUtils.getIntegersFromDataEnums(VehicleGear.class);
for (int gear : gears) {
String gearString = VehicleGear.toString(gear);
assertThat(gearString.startsWith("0x")).isFalse();
}
}
- // Get all enums from the class.
- private static List<Integer> getIntegersFromDataEnums(Class clazz) {
- Field[] fields = clazz.getDeclaredFields();
- List<Integer> integerList = new ArrayList<>(5);
- for (Field f : fields) {
- if (f.getType() == int.class) {
- try {
- integerList.add(f.getInt(clazz));
- } catch (IllegalAccessException | RuntimeException e) {
- Log.w(TAG, "Failed to get value");
- }
- }
- }
- return integerList;
- }
}
diff --git a/tests/tests/car/src/android/car/cts/VehicleIgnitionStateTest.java b/tests/tests/car/src/android/car/cts/VehicleIgnitionStateTest.java
new file mode 100644
index 0000000..fe4e14c
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/VehicleIgnitionStateTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.car.VehicleIgnitionState;
+import android.car.cts.utils.VehiclePropertyUtils;
+
+import org.junit.Test;
+
+import java.util.List;
+
+public final class VehicleIgnitionStateTest {
+
+ @Test
+ public void testToString() {
+ assertThat(VehicleIgnitionState.toString(VehicleIgnitionState.UNDEFINED))
+ .isEqualTo("UNDEFINED");
+ assertThat(VehicleIgnitionState.toString(VehicleIgnitionState.LOCK)).isEqualTo("LOCK");
+ assertThat(VehicleIgnitionState.toString(VehicleIgnitionState.OFF)).isEqualTo("OFF");
+ assertThat(VehicleIgnitionState.toString(VehicleIgnitionState.ACC)).isEqualTo("ACC");
+ assertThat(VehicleIgnitionState.toString(VehicleIgnitionState.ON)).isEqualTo("ON");
+ assertThat(VehicleIgnitionState.toString(VehicleIgnitionState.START)).isEqualTo("START");
+ }
+
+ @Test
+ public void testAllIgnitionStatesAreMappedInToString() {
+ List<Integer> ignitionStates =
+ VehiclePropertyUtils.getIntegersFromDataEnums(VehicleIgnitionState.class);
+ for (Integer ignitionState : ignitionStates) {
+ String ignitionStateString = VehicleIgnitionState.toString(ignitionState);
+ assertWithMessage("%s starts with 0x", ignitionStateString)
+ .that(ignitionStateString.startsWith("0x")).isFalse();
+ }
+ }
+}
+
diff --git a/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java b/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java
index 4d00ea9..58d7274 100644
--- a/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java
+++ b/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java
@@ -19,40 +19,21 @@
import static com.google.common.truth.Truth.assertThat;
import android.car.VehiclePropertyIds;
+import android.car.cts.utils.VehiclePropertyUtils;
import android.platform.test.annotations.RequiresDevice;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
import java.util.List;
@SmallTest
@RequiresDevice
@RunWith(AndroidJUnit4.class)
public class VehiclePropertyIdsTest {
- private static final String TAG = "VehiclePropertyIdsTest";
-
- // Get all enums from the class.
- private static List<Integer> getIntegersFromDataEnums() {
- Field[] fields = VehiclePropertyIds.class.getDeclaredFields();
- List<Integer> integerList = new ArrayList<>(5);
- for (Field f : fields) {
- if (f.getType() == int.class) {
- try {
- integerList.add(f.getInt(VehiclePropertyIds.class));
- } catch (IllegalAccessException | RuntimeException e) {
- Log.w(TAG, "Failed to get value");
- }
- }
- }
- return integerList;
- }
/**
* Test for {@link VehiclePropertyIds#toString()}
@@ -331,7 +312,8 @@
*/
@Test
public void testAllPropertiesAreMappedInToString() {
- List<Integer> systemProperties = getIntegersFromDataEnums();
+ List<Integer> systemProperties =
+ VehiclePropertyUtils.getIntegersFromDataEnums(VehiclePropertyIds.class);
for (int propertyId : systemProperties) {
String propertyString = VehiclePropertyIds.toString(propertyId);
assertThat(propertyString.startsWith("0x")).isFalse();
diff --git a/tests/tests/car/src/android/car/cts/utils/VehiclePropertyUtils.java b/tests/tests/car/src/android/car/cts/utils/VehiclePropertyUtils.java
new file mode 100644
index 0000000..ad88306
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/utils/VehiclePropertyUtils.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.utils;
+
+import android.util.Log;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+public class VehiclePropertyUtils {
+
+ private VehiclePropertyUtils() {
+ }
+
+ /** Returns all integer type enums from the class */
+ public static List<Integer> getIntegersFromDataEnums(Class<?> clazz) {
+ Field[] fields = clazz.getDeclaredFields();
+ List<Integer> integerList = new ArrayList<>(5);
+ for (Field f : fields) {
+ if (f.getType() == int.class) {
+ try {
+ integerList.add(f.getInt(clazz));
+ } catch (IllegalAccessException | RuntimeException e) {
+ Log.w(clazz.getSimpleName(), "Failed to get value");
+ }
+ }
+ }
+ return integerList;
+ }
+}
diff --git a/tests/tests/car_builtin/Android.bp b/tests/tests/car_builtin/Android.bp
new file mode 100644
index 0000000..2d2a179
--- /dev/null
+++ b/tests/tests/car_builtin/Android.bp
@@ -0,0 +1,47 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsCarBuiltinApiTestCases",
+ defaults: ["cts_defaults"],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.aidl",
+ ],
+ resource_dirs: ["res"],
+ libs: [
+ "android.car.builtin",
+ "android.test.base",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.ext.truth",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "cts-wm-util",
+ "testng",
+ ],
+
+ platform_apis: true,
+ sdk_version: "module_current",
+}
diff --git a/tests/tests/car_builtin/AndroidManifest.xml b/tests/tests/car_builtin/AndroidManifest.xml
new file mode 100644
index 0000000..56f46ba
--- /dev/null
+++ b/tests/tests/car_builtin/AndroidManifest.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.car.cts.builtin">
+ <uses-feature android:name="android.hardware.type.automotive" />
+
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+
+ <application android:description="@string/app_description">
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name="android.car.cts.builtin.activity.SimpleActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.car.cts.builtin.activity.VirtualDisplayIdTestActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <service android:name="android.car.cts.builtin.os.SharedMemoryTestService"
+ android:process=":shdmemservice">
+ </service>
+ <service android:name="android.car.cts.builtin.os.ServiceManagerTestService"
+ android:process=":testservice">
+ </service>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.car.cts.builtin"
+ android:label="CTS tests for car builtin api">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
+
diff --git a/tests/tests/car_builtin/AndroidTest.xml b/tests/tests/car_builtin/AndroidTest.xml
new file mode 100644
index 0000000..9532903
--- /dev/null
+++ b/tests/tests/car_builtin/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 car builtin api test cases">
+ <object class="com.android.tradefed.testtype.suite.module.CarModuleController"
+ type="module_controller"/>
+ <option name="test-suite-tag" value="cts"/>
+ <option name="config-descriptor:metadata" key="component" value="auto"/>
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app"/>
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi"/>
+
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="CtsCarBuiltinApiTestCases.apk"/>
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.car.cts.builtin"/>
+ </test>
+</configuration>
diff --git a/tests/tests/car_builtin/OWNERS b/tests/tests/car_builtin/OWNERS
new file mode 100644
index 0000000..a8e927d
--- /dev/null
+++ b/tests/tests/car_builtin/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 526266
+keunyoung@google.com
+felipeal@google.com
+gurunagarajan@google.com
diff --git a/tests/tests/car_builtin/res/values/strings.xml b/tests/tests/car_builtin/res/values/strings.xml
new file mode 100644
index 0000000..3c9ef64
--- /dev/null
+++ b/tests/tests/car_builtin/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="app_description">CTS Tests for Car Builtin APIs</string>
+</resources>
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/CarBuiltinTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/CarBuiltinTest.java
new file mode 100644
index 0000000..a323b22
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/CarBuiltinTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.builtin.CarBuiltin;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class CarBuiltinTest {
+ @Test
+ public void testMinorVersion() {
+ assertThat(CarBuiltin.API_VERSION_MINOR_INT).isAtLeast(0);
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/PermissionHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/PermissionHelperTest.java
new file mode 100644
index 0000000..fdc07b7
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/PermissionHelperTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.builtin.PermissionHelper;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class PermissionHelperTest {
+
+ private static final String EXPECTED_MONITOR_INPUT_PERMISSION_STRING =
+ "android.permission.MONITOR_INPUT";
+
+ @Test
+ public void testMonitorInputPermissionString() {
+ assertThat(PermissionHelper.MONITOR_INPUT)
+ .isEqualTo(EXPECTED_MONITOR_INPUT_PERMISSION_STRING);
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/activity/SimpleActivity.java b/tests/tests/car_builtin/src/android/car/cts/builtin/activity/SimpleActivity.java
new file mode 100644
index 0000000..d111f71
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/activity/SimpleActivity.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.activity;
+
+import android.app.Activity;
+
+/**
+ * A very simple activity for testing PackageManagerHelper.
+ */
+public final class SimpleActivity extends Activity {
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/activity/VirtualDisplayIdTestActivity.java b/tests/tests/car_builtin/src/android/car/cts/builtin/activity/VirtualDisplayIdTestActivity.java
new file mode 100644
index 0000000..03542eb
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/activity/VirtualDisplayIdTestActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.activity;
+
+import android.app.Activity;
+import android.car.builtin.content.ContextHelper;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Display;
+
+public final class VirtualDisplayIdTestActivity extends Activity {
+ public static final String TAG = VirtualDisplayIdTestActivity.class.getSimpleName();
+
+ private static int sDisplayId = Display.INVALID_DISPLAY;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ sDisplayId = ContextHelper.getDisplayId(this);
+ Log.d(TAG, "onCreate: sDisplayId=" + sDisplayId);
+ }
+
+ public static int getDisplayId() {
+ return sDisplayId;
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java
new file mode 100644
index 0000000..4e0919c
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.builtin.app.ActivityManagerHelper;
+import android.content.pm.PackageManager;
+import android.os.Process;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class ActivityManagerHelperTest {
+
+ private static final String TAG = ActivityManagerHelperTest.class.getSimpleName();
+
+ private static final String NOT_REQUESTED_PERMISSION_CAR_MILEAGE =
+ "android.car.permission.CAR_MILEAGE";
+ private static final String NOT_REQUESTED_PERMISSION_READ_CAR_POWER_POLICY =
+ "android.car.permission.READ_CAR_POWER_POLICY";
+
+ private static final String GRANTED_PERMISSION_INTERACT_ACROSS_USERS =
+ "android.permission.INTERACT_ACROSS_USERS";
+
+ private static final int OWNING_UID = -1;
+
+ @Test
+ public void testCheckComponentPermission() throws Exception {
+ // not requested from Manifest
+ assertComponentPermissionNotGranted(NOT_REQUESTED_PERMISSION_CAR_MILEAGE);
+ assertComponentPermissionNotGranted(NOT_REQUESTED_PERMISSION_READ_CAR_POWER_POLICY);
+
+ // requested from Manifest and granted
+ assertComponentPermissionGranted(GRANTED_PERMISSION_INTERACT_ACROSS_USERS);
+ }
+
+ //TODO(b/201005730): add as many as feasible test cases to cover more
+ // ActivityManagerHelper APIs.
+
+ private void assertComponentPermissionGranted(String permission) throws Exception {
+ assertThat(ActivityManagerHelper.checkComponentPermission(permission,
+ Process.myUid(), /* owningUid= */ OWNING_UID, /* exported= */ true))
+ .isEqualTo(PackageManager.PERMISSION_GRANTED);
+ }
+
+ private void assertComponentPermissionNotGranted(String permission) throws Exception {
+ assertThat(ActivityManagerHelper.checkComponentPermission(permission,
+ Process.myUid(), /* owningUid= */ OWNING_UID, /* exported= */ true))
+ .isEqualTo(PackageManager.PERMISSION_DENIED);
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/app/DisplayUtils.java b/tests/tests/car_builtin/src/android/car/cts/builtin/app/DisplayUtils.java
new file mode 100644
index 0000000..b2448c3
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/app/DisplayUtils.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.app;
+
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.os.Looper;
+import android.util.DisplayMetrics;
+import android.view.Display;
+
+import com.android.compatibility.common.util.TestUtils;
+
+/**
+ * Utilities needed when interacting with the display
+ */
+public class DisplayUtils {
+ private static final int DISPLAY_ADDED_TIMEOUT_MS = 10_000;
+ private static final String CONTEXT_HELPER_CTS_DISPLAY_NAME = "ContextHelperCtsVirtualDisplay";
+
+ public static class VirtualDisplaySession implements AutoCloseable {
+ private VirtualDisplay mVirtualDisplay;
+ private ImageReader mReader;
+
+ /**
+ * Creates a virtual display having same size with default display and waits until it's
+ * in display list. The density of the virtual display is based on
+ * {@link DisplayMetrics#xdpi} so that the threshold of gesture detection is same as
+ * the default display's.
+ *
+ * @param context
+ * @param isPrivate if this display is a private display.
+ * @return virtual display.
+ *
+ * @throws IllegalStateException if called from main thread.
+ */
+ public Display createDisplayWithDefaultDisplayMetricsAndWait(Context context,
+ boolean isPrivate) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ throw new IllegalStateException("Should not call from main thread");
+ }
+
+ if (mReader != null) {
+ throw new IllegalStateException(
+ "Only one display can be created during this session.");
+ }
+
+ DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ DisplayMetrics metrics = new DisplayMetrics();
+ displayManager.getDisplay(DEFAULT_DISPLAY).getRealMetrics(metrics);
+
+ mReader = ImageReader.newInstance(metrics.widthPixels, metrics.heightPixels,
+ PixelFormat.RGBA_8888, 1 /* maxImages */);
+
+ Object waitObject = new Object();
+ DisplayManager.DisplayListener listener = new DisplayManager.DisplayListener() {
+ @Override
+ public void onDisplayAdded(int i) {
+ synchronized (waitObject) {
+ waitObject.notifyAll();
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int i) {
+ }
+
+ @Override
+ public void onDisplayChanged(int i) {
+ }
+ };
+
+ displayManager.registerDisplayListener(listener, null);
+
+ int flags = isPrivate ? 0
+ : (VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | VIRTUAL_DISPLAY_FLAG_PUBLIC);
+
+ mVirtualDisplay = displayManager.createVirtualDisplay(CONTEXT_HELPER_CTS_DISPLAY_NAME,
+ metrics.widthPixels, metrics.heightPixels, (int) metrics.xdpi,
+ mReader.getSurface(), flags);
+
+ try {
+ int theNewDisplayId = mVirtualDisplay.getDisplay().getDisplayId();
+ TestUtils.waitOn(waitObject,
+ () -> displayManager.getDisplay(theNewDisplayId) != null,
+ DISPLAY_ADDED_TIMEOUT_MS,
+ String.format("wait for virtual display %d adding", theNewDisplayId));
+ } finally {
+ displayManager.unregisterDisplayListener(listener);
+ }
+
+ return mVirtualDisplay.getDisplay();
+ }
+
+ @Override
+ public void close() {
+ if (mVirtualDisplay != null) {
+ mVirtualDisplay.release();
+ }
+ if (mReader != null) {
+ mReader.close();
+ }
+ }
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/app/KeyguardManagerHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/app/KeyguardManagerHelperTest.java
new file mode 100644
index 0000000..6cc2071
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/app/KeyguardManagerHelperTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.app;
+
+import static android.server.wm.UiDeviceUtils.pressUnlockButton;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.car.builtin.app.KeyguardManagerHelper;
+import android.server.wm.ActivityManagerTestBase;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class KeyguardManagerHelperTest extends ActivityManagerTestBase {
+
+ private static final String TAG = KeyguardManagerHelperTest.class.getSimpleName();
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ assumeTrue(supportsSecureLock());
+ }
+
+ @Test
+ public void testIsKeyguardLocked() throws Exception {
+ try (LockScreenSession lockScreenSession = createManagedLockScreenSession()) {
+ lockScreenSession.setLockCredential().gotoKeyguard();
+ assertThat(KeyguardManagerHelper.isKeyguardLocked()).isTrue();
+
+ unlockDevice();
+ lockScreenSession.enterAndConfirmLockCredential();
+ mWmState.waitAndAssertKeyguardGone();
+ assertThat(KeyguardManagerHelper.isKeyguardLocked()).isFalse();
+ }
+ }
+
+ private void unlockDevice() {
+ touchAndCancelOnDisplayCenterSync(DEFAULT_DISPLAY);
+ pressUnlockButton();
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/content/ContextHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/content/ContextHelperTest.java
new file mode 100644
index 0000000..927f9a6
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/content/ContextHelperTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.content;
+
+import static android.car.cts.builtin.app.DisplayUtils.VirtualDisplaySession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.car.builtin.content.ContextHelper;
+import android.car.cts.builtin.activity.VirtualDisplayIdTestActivity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.server.wm.ActivityManagerTestBase;
+import android.view.Display;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class ContextHelperTest extends ActivityManagerTestBase {
+ private static final int ACTIVITY_FOCUS_TIMEOUT_MS = 10_000;
+
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+ @Test
+ public void testDefaultDisplayId() throws Exception {
+ // execution and assertion
+ assertThat(ContextHelper.getDisplayId(mContext)).isEqualTo(Display.DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testVirtualDisplayId() throws Exception {
+ // check the assumption
+ String requiredFeature = PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(requiredFeature));
+
+ try (VirtualDisplaySession session = new VirtualDisplaySession()) {
+ // setup: create a virtual display
+ int createdVirtualDisplayId = session
+ .createDisplayWithDefaultDisplayMetricsAndWait(mContext, true).getDisplayId();
+
+ assertNotEquals(createdVirtualDisplayId, Display.DEFAULT_DISPLAY);
+ assertNotEquals(createdVirtualDisplayId, Display.INVALID_DISPLAY);
+
+ // execution: launch VirtualDisplayIdActivity in the virtual display
+ launchVirtualDisplayIdTestActivity(createdVirtualDisplayId, mContext.getPackageName(),
+ VirtualDisplayIdTestActivity.class.getName());
+
+ // assertion
+ assertEquals(createdVirtualDisplayId, VirtualDisplayIdTestActivity.getDisplayId());
+ }
+ }
+
+ private void launchVirtualDisplayIdTestActivity(int displayId,
+ String pkgName, String activityClassName) {
+ ComponentName testActivity = new ComponentName(pkgName, activityClassName);
+ launchActivityOnDisplay(testActivity, displayId);
+ waitForActivityFocused(ACTIVITY_FOCUS_TIMEOUT_MS, testActivity);
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/content/pm/PackageManagerHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/content/pm/PackageManagerHelperTest.java
new file mode 100644
index 0000000..4b2be89
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/content/pm/PackageManagerHelperTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.builtin.content.pm.PackageManagerHelper;
+import android.car.cts.builtin.R;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+public final class PackageManagerHelperTest {
+
+ private static final String TAG = PackageManagerHelperTest.class.getSimpleName();
+ private static final String ANDROID_CAR_PKG = "com.android.car";
+ private static final String CAR_BUILTIN_CTS_PKG = "android.car.cts.builtin";
+
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ private final PackageManager mPackageManager = mContext.getPackageManager();
+
+ @Test
+ public void testGetPackageInfoAsUser() throws Exception {
+ // setup
+ String expectedActivityName = "android.car.cts.builtin.activity.SimpleActivity";
+ int flags = PackageManager.GET_ACTIVITIES | PackageManager.GET_INSTRUMENTATION
+ | PackageManager.GET_SERVICES;
+ int curUser = UserHandle.myUserId();
+
+ // execution
+ PackageInfo pkgInfoUser = PackageManagerHelper.getPackageInfoAsUser(mPackageManager,
+ CAR_BUILTIN_CTS_PKG, flags, curUser);
+ ApplicationInfo appInfo = pkgInfoUser.applicationInfo;
+ ActivityInfo[] activities = pkgInfoUser.activities;
+ ServiceInfo[] services = pkgInfoUser.services;
+
+ // assertion
+ assertThat(appInfo).isNotNull();
+ assertThat(appInfo.descriptionRes).isEqualTo(R.string.app_description);
+ assertThat(activities).isNotNull();
+ assertThat(hasActivity(expectedActivityName, activities)).isTrue();
+ assertThat(services).isNotNull();
+ }
+
+ @Test
+ public void testAppTypeChecking() throws Exception {
+ // setup
+ ApplicationInfo systemApp = mPackageManager
+ .getApplicationInfo(ANDROID_CAR_PKG, /* flags= */ 0);
+ ApplicationInfo ctsApp = mPackageManager
+ .getApplicationInfo(CAR_BUILTIN_CTS_PKG, /* flags= */ 0);
+
+ // execution and assertion
+ assertThat(PackageManagerHelper.isSystemApp(systemApp)).isTrue();
+ assertThat(PackageManagerHelper.isUpdatedSystemApp(systemApp)).isFalse();
+ assertThat(PackageManagerHelper.isSystemExtApp(systemApp)).isFalse();
+ assertThat(PackageManagerHelper.isOemApp(systemApp)).isFalse();
+ assertThat(PackageManagerHelper.isOdmApp(systemApp)).isFalse();
+ assertThat(PackageManagerHelper.isVendorApp(systemApp)).isFalse();
+ assertThat(PackageManagerHelper.isProductApp(systemApp)).isFalse();
+
+ assertThat(PackageManagerHelper.isSystemApp(ctsApp)).isFalse();
+ assertThat(PackageManagerHelper.isUpdatedSystemApp(ctsApp)).isFalse();
+ assertThat(PackageManagerHelper.isSystemExtApp(ctsApp)).isFalse();
+ assertThat(PackageManagerHelper.isOemApp(ctsApp)).isFalse();
+ assertThat(PackageManagerHelper.isOdmApp(ctsApp)).isFalse();
+ assertThat(PackageManagerHelper.isVendorApp(ctsApp)).isFalse();
+ assertThat(PackageManagerHelper.isProductApp(ctsApp)).isFalse();
+ }
+
+ //TODO (b/201822684) Add more test cases to cover more PackageManagerHelper APIs
+
+ private boolean hasActivity(String activityName, ActivityInfo[] activities) {
+ return Arrays.stream(activities).anyMatch(a -> activityName.equals(a.name));
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/BuildHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/BuildHelperTest.java
new file mode 100644
index 0000000..aab86c7
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/BuildHelperTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.os;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.car.builtin.os.BuildHelper;
+import android.os.Build;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class BuildHelperTest {
+
+ private static final String TAG = BuildHelperTest.class.getSimpleName();
+ private static final String BUILD_TYPE_USER = "user";
+ private static final String BUILD_TYPE_ENG = "eng";
+ private static final String BUILD_TYPE_USER_DEBUG = "userdebug";
+
+ @Test
+ public void testBuildTypeCheck() throws Exception {
+ switch (Build.TYPE) {
+ case BUILD_TYPE_USER:
+ assertTrue(BuildHelper.isUserBuild());
+ assertFalse(BuildHelper.isUserDebugBuild());
+ assertFalse(BuildHelper.isEngBuild());
+ assertFalse(BuildHelper.isDebuggableBuild());
+ break;
+ case BUILD_TYPE_USER_DEBUG:
+ assertFalse(BuildHelper.isUserBuild());
+ assertTrue(BuildHelper.isUserDebugBuild());
+ assertFalse(BuildHelper.isEngBuild());
+ assertTrue(BuildHelper.isDebuggableBuild());
+ break;
+ case BUILD_TYPE_ENG:
+ assertFalse(BuildHelper.isUserBuild());
+ assertFalse(BuildHelper.isUserDebugBuild());
+ assertTrue(BuildHelper.isEngBuild());
+ assertTrue(BuildHelper.isDebuggableBuild());
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown Build Type: " + Build.TYPE);
+ }
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/IServiceManagerTestService.aidl b/tests/tests/car_builtin/src/android/car/cts/builtin/os/IServiceManagerTestService.aidl
new file mode 100644
index 0000000..52aa34d
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/IServiceManagerTestService.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.os;
+
+interface IServiceManagerTestService {
+ int echo(in int val);
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/ISharedMemoryTestService.aidl b/tests/tests/car_builtin/src/android/car/cts/builtin/os/ISharedMemoryTestService.aidl
new file mode 100644
index 0000000..b36aecde
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/ISharedMemoryTestService.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.os;
+
+import android.os.ParcelFileDescriptor;
+
+interface ISharedMemoryTestService {
+ int readBufData(in ParcelFileDescriptor pfd);
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/ParcelHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/ParcelHelperTest.java
new file mode 100644
index 0000000..b46e26d
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/ParcelHelperTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.builtin.os.ParcelHelper;
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArraySet;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class ParcelHelperTest {
+
+ private static final String TAG = ParcelHelperTest.class.getSimpleName();
+ private final String[] mInitData = {"Hello, ", "Android", "Auto", "!"};
+
+ @Test
+ public void testBlobAccess() {
+ String blobContent = "Hello, Android Auto!";
+ Parcel p = Parcel.obtain();
+
+ // setup
+ byte[] inBlob = blobContent.getBytes();
+ ParcelHelper.writeBlob(p, inBlob);
+ p.setDataPosition(0);
+
+ // execution
+ byte[] outBlob = ParcelHelper.readBlob(p);
+
+ // assertion
+ assertThat(outBlob).isEqualTo(inBlob);
+ }
+
+ @Test
+ public void testArraySetAccess() {
+ ArraySet<Object> inputSet = new ArraySet<>(mInitData);
+ ArraySet<?> outputSet = null;
+ Parcel p = Parcel.obtain();
+
+ // setup
+ ArraySet emptySet = ParcelHelper.readArraySet(p, /* loader = */ null);
+ assertThat(emptySet.size()).isEqualTo(0);
+ ParcelHelper.writeArraySet(p, inputSet);
+ p.setDataPosition(0);
+
+ // execution
+ outputSet = ParcelHelper.readArraySet(p, String.class.getClassLoader());
+
+ // assertion
+ assertThat(outputSet).containsExactlyElementsIn(inputSet);
+ }
+
+ @Test
+ public void testStringArrayAccess() {
+ String[] inputArray = mInitData;
+ Parcel p = Parcel.obtain();
+
+ // setup
+ p.writeStringArray(inputArray);
+ p.setDataPosition(0);
+
+ // execution
+ String[] outputArray = ParcelHelper.readStringArray(p);
+
+ // assertion
+ assertThat(outputArray).isEqualTo(inputArray);
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/ServiceManagerHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/ServiceManagerHelperTest.java
new file mode 100644
index 0000000..36e76be
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/ServiceManagerHelperTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.os;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.app.Instrumentation;
+import android.car.builtin.os.ServiceManagerHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ServiceManagerHelperTest {
+
+ private static final String SERVICE_PACKAGE_NAME = "android.car.cts.builtin";
+
+ private static final String[] SYSTEM_SERVICES = {
+ Context.ACTIVITY_SERVICE,
+ Context.ALARM_SERVICE,
+ Context.AUDIO_SERVICE,
+ Context.DISPLAY_SERVICE,
+ Context.INPUT_SERVICE,
+ Context.JOB_SCHEDULER_SERVICE,
+ Context.LOCATION_SERVICE,
+ Context.POWER_SERVICE,
+ Context.WINDOW_SERVICE
+ };
+ private static final int TIMEOUT = 20_000;
+
+ private Instrumentation mInstrumentation;
+ private PeerConnection mRemoteConnection;
+ private IServiceManagerTestService mRemoteService;
+
+ public static class PeerConnection implements ServiceConnection {
+ private final CountDownLatch mServiceReadyLatch = new CountDownLatch(1);
+
+ private IServiceManagerTestService mService;
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mService = IServiceManagerTestService.Stub.asInterface(service);
+ mServiceReadyLatch.countDown();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+
+ public void waitForServiceReady() throws Exception {
+ mServiceReadyLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+
+ public IServiceManagerTestService getService() {
+ return mService;
+ }
+ }
+
+ @Test
+ public void testInitServiceCache() throws Exception {
+ // The service manager keeps a cache of system services during
+ // initialization
+ for (String serviceName : SYSTEM_SERVICES) {
+ assertNotNull(ServiceManagerHelper.checkService(serviceName));
+ assertNotNull(ServiceManagerHelper.getService(serviceName));
+ }
+ }
+
+ @Test
+ public void testServiceCacheWithCreatedService() throws Exception {
+ // setup a new service
+ String testServiceName = ServiceManagerTestService.class.getName();
+ Random randomGenerator = new Random();
+ int maxCount = 10;
+
+ setUpService();
+
+ // assert the new service works
+ for (int i = 0; i < maxCount; i++) {
+ int val = randomGenerator.nextInt();
+ assertEquals(val, mRemoteService.echo(val));
+ }
+
+ // assert the newly created service is not in the ServiceManager cache
+ assertNull(ServiceManagerHelper.checkService(testServiceName));
+ assertNull(ServiceManagerHelper.getService(testServiceName));
+ }
+
+ private void setUpService() throws Exception {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ Context context = mInstrumentation.getContext();
+ // Bring up both remote processes and wire them to each other
+ Intent remoteIntent = new Intent();
+ remoteIntent.setComponent(new ComponentName(SERVICE_PACKAGE_NAME,
+ ServiceManagerTestService.class.getName()));
+ mRemoteConnection = new PeerConnection();
+ getContext().bindService(remoteIntent, mRemoteConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
+
+ mRemoteConnection.waitForServiceReady();
+ mRemoteService = mRemoteConnection.getService();
+ assertNotNull(mRemoteService);
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/ServiceManagerTestService.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/ServiceManagerTestService.java
new file mode 100644
index 0000000..8a8d15a
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/ServiceManagerTestService.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.os;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+public final class ServiceManagerTestService extends Service {
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new ServiceImpl();
+ }
+
+ private static class ServiceImpl extends IServiceManagerTestService.Stub {
+ @Override
+ public int echo(int val) throws RemoteException {
+ return val;
+ }
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryFileDescriptorTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryFileDescriptorTest.java
new file mode 100644
index 0000000..7f1c517
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryFileDescriptorTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.os;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Instrumentation;
+import android.car.builtin.os.SharedMemoryHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.system.ErrnoException;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class SharedMemoryFileDescriptorTest {
+
+ private static final int TIMEOUT = 20_000;
+ private Instrumentation mInstrumentation;
+ private Intent mRemoteIntent;
+ private PeerConnection mRemoteConnection;
+ private ISharedMemoryTestService mRemote;
+
+ public static class PeerConnection implements ServiceConnection {
+ private final CountDownLatch mServiceReadyLatch = new CountDownLatch(1);
+
+ private ISharedMemoryTestService mTestService = null;
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mTestService = ISharedMemoryTestService.Stub.asInterface(service);
+ mServiceReadyLatch.countDown();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+
+ public ISharedMemoryTestService get() throws Exception {
+ mServiceReadyLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+ return mTestService;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ Context context = mInstrumentation.getContext();
+ // Bring up both remote processes and wire them to each other
+ mRemoteIntent = new Intent();
+ mRemoteIntent.setComponent(new ComponentName(
+ "android.car.cts.builtin", "android.car.cts.builtin.os.SharedMemoryTestService"));
+ mRemoteConnection = new PeerConnection();
+ getContext().bindService(mRemoteIntent, mRemoteConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
+
+ mRemote = mRemoteConnection.get();
+ assertNotNull(mRemote);
+ }
+
+ @After
+ public void tearDown() {
+ Context context = mInstrumentation.getContext();
+ context.unbindService(mRemoteConnection);
+ }
+
+ @Test
+ public void testReadBufData() throws RemoteException, ErrnoException, IOException {
+ // setup
+ int memSize = 32 * 1024;
+
+ SharedMemory sharedMem = SharedMemory.create(/* name */ null, memSize);
+ ByteBuffer buffer = null;
+ try {
+ buffer = sharedMem.mapReadWrite();
+ int checksum = 0;
+ for (int i = 0; i < memSize; i++) {
+ buffer.put((byte) i);
+ checksum += (byte) i;
+ }
+
+ // execution
+ ParcelFileDescriptor pfd = SharedMemoryHelper.createParcelFileDescriptor(sharedMem);
+ int returnedChecksum = mRemote.readBufData(pfd);
+
+ // assertion
+ assertEquals(checksum, returnedChecksum);
+ } finally {
+ // teardown
+ SharedMemory.unmap(buffer);
+ }
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryHelperTest.java
new file mode 100644
index 0000000..a231bbe
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryHelperTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.os;
+
+import static org.junit.Assert.assertEquals;
+
+import android.car.builtin.os.SharedMemoryHelper;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.os.SharedMemory;
+import android.system.OsConstants;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+public final class SharedMemoryHelperTest {
+ private static final String TAG = SharedMemoryHelperTest.class.getSimpleName();
+
+ @Test
+ public void testCreateParcelFileDescriptor() throws Exception {
+ // setup
+ int memSize = 32 * 1024;
+ int bufSize = memSize / 4;
+ SharedMemory sm = SharedMemory.create(/* name */ null, memSize);
+ ByteBuffer bb = sm.map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, bufSize);
+ populateBufferSequentially(bb, bufSize);
+
+ // execution
+ ParcelFileDescriptor pfd = SharedMemoryHelper.createParcelFileDescriptor(sm);
+
+ // assertion
+ try (AutoCloseInputStream in = new AutoCloseInputStream(pfd)) {
+ bb.rewind();
+ for (int i = 0; i < bufSize; i++) {
+ assertEquals(bb.get(), (byte) in.read());
+ }
+ }
+
+ // teardown
+ SharedMemory.unmap(bb);
+ }
+
+ @Test
+ public void testSharedMemoryCreation() throws Exception {
+ // setup
+ int memSize = 32 * 1024;
+ int bufSize = memSize / 4;
+ SharedMemory sm1 = SharedMemory.create(/* name */ null, memSize);
+ ByteBuffer bb1 = sm1.map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, bufSize);
+ populateBufferSequentially(bb1, bufSize);
+
+ // execution
+ ParcelFileDescriptor pfd = SharedMemoryHelper.createParcelFileDescriptor(sm1);
+ Parcel p = Parcel.obtain();
+ p.writeFileDescriptor(pfd.getFileDescriptor());
+ p.setDataPosition(0);
+ SharedMemory sm2 = SharedMemory.CREATOR.createFromParcel(p);
+ ByteBuffer bb2 = sm2.map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, bufSize);
+
+ // assertion
+ bb1.rewind();
+ assertEquals(bb1, bb2);
+
+ // teardown
+ SharedMemory.unmap(bb1);
+ SharedMemory.unmap(bb2);
+ }
+
+ private void populateBufferSequentially(ByteBuffer buf, int length) {
+ for (int i = 0; i < length; i++) {
+ buf.put((byte) i);
+ }
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryTestService.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryTestService.java
new file mode 100644
index 0000000..7f95ec8
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryTestService.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.os;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.system.ErrnoException;
+
+import java.nio.ByteBuffer;
+
+public class SharedMemoryTestService extends Service {
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new ServiceImpl();
+ }
+
+ private static class ServiceImpl extends ISharedMemoryTestService.Stub {
+ @Override
+ public int readBufData(ParcelFileDescriptor pfd) throws RemoteException {
+ int checksum = 0;
+ SharedMemory sharedMemory = null;
+ ByteBuffer mappedBuffer = null;
+
+ try {
+ sharedMemory = SharedMemory.fromFileDescriptor(pfd);
+ mappedBuffer = sharedMemory.mapReadOnly();
+ for (int i = 0; i < sharedMemory.getSize(); i++) {
+ checksum += mappedBuffer.get();
+ }
+ } catch (ErrnoException ex) {
+ throw new RuntimeException(ex);
+ } finally {
+ SharedMemory.unmap(mappedBuffer);
+ }
+
+ return checksum;
+ }
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/SystemPropertiesHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SystemPropertiesHelperTest.java
new file mode 100644
index 0000000..9b915f3
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SystemPropertiesHelperTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.builtin.os.SystemPropertiesHelper;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class SystemPropertiesHelperTest {
+ private static final String TAG = SystemPropertiesHelperTest.class.getSimpleName();
+
+ // a temporary SystemProperty for CTS. it will be cleared after device reboot.
+ private static final String CTS_TEST_PROPERTY_KEY = "cts.car.builtin_property_helper.String";
+ private static final String CTS_TEST_PROPERTY_VAL = "SystemPropertiesHelperTest";
+
+ @Test
+ public void testSet() {
+ SystemPropertiesHelper.set(CTS_TEST_PROPERTY_KEY, CTS_TEST_PROPERTY_VAL);
+ String val = SystemProperties.get(CTS_TEST_PROPERTY_KEY);
+ Log.d(TAG, val);
+ assertThat(val).isEqualTo(CTS_TEST_PROPERTY_VAL);
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/TraceHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/TraceHelperTest.java
new file mode 100644
index 0000000..2bdd069
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/TraceHelperTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.builtin.os.TraceHelper;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class TraceHelperTest {
+
+ // the value is from frameworks/base/core/java/android/os/Trace.java
+ private static final long EXPECTED_CAR_SERVICE_TRACE_TAG = 1L << 19;
+
+ @Test
+ public void testCarServiceTraceTag() {
+ assertThat(TraceHelper.TRACE_TAG_CAR_SERVICE).isEqualTo(EXPECTED_CAR_SERVICE_TRACE_TAG);
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/UserManagerHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/UserManagerHelperTest.java
new file mode 100644
index 0000000..1a24cdd
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/UserManagerHelperTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.car.builtin.os.UserManagerHelper;
+import android.content.Context;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public final class UserManagerHelperTest {
+
+ private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+ private Context mContext;
+ private UserManager mUserManager;
+
+ @Before
+ public void setup() {
+ mContext = mInstrumentation.getContext();
+ mUserManager = mContext.getSystemService(UserManager.class);
+ }
+
+ @Test
+ public void testPreCreateUser_fullUser() {
+ preCreateUserTest(UserManager.USER_TYPE_FULL_SECONDARY);
+ }
+
+ @Test
+ public void testPreCreateUser_guestUser() {
+ preCreateUserTest(UserManager.USER_TYPE_FULL_GUEST);
+ }
+
+ private void preCreateUserTest(String type) {
+ try {
+ mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
+ android.Manifest.permission.CREATE_USERS);
+
+ UserHandle user = UserManagerHelper.preCreateUser(mUserManager, type);
+
+ assertPrecreatedUserExists(user, type);
+ } finally {
+ mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+ }
+ }
+
+ private void assertPrecreatedUserExists(UserHandle user, String type) {
+ assertThat(user).isNotNull();
+ try {
+ String allUsers = SystemUtil.runShellCommand("cmd user list --all -v");
+ String[] result = allUsers.split("\n");
+ for (int i = 0; i < result.length; i++) {
+ if (result[i].contains("id=" + user.getIdentifier())) {
+ assertThat(result[i]).contains("(pre-created)");
+ if (type == UserManager.USER_TYPE_FULL_SECONDARY) {
+ assertThat(result[i]).contains("type=full.SECONDARY");
+ }
+ if (type == UserManager.USER_TYPE_FULL_GUEST) {
+ assertThat(result[i]).contains("type=full.GUEST");
+ }
+ return;
+ }
+ }
+ fail("User not found. All users: " + allUsers + ". Expected user: " + user);
+ } finally {
+ // Remove User
+ mUserManager.removeUser(user);
+ }
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/AssistUtilsHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/AssistUtilsHelperTest.java
new file mode 100644
index 0000000..8183afd
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/AssistUtilsHelperTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.util;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.car.builtin.util.AssistUtilsHelper;
+import android.content.Context;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public final class AssistUtilsHelperTest {
+ private static final String TAG = AssistUtilsHelper.class.getSimpleName();
+ private static final String PERMISSION_ACCESS_VOICE_INTERACTION_SERVICE =
+ "android.permission.ACCESS_VOICE_INTERACTION_SERVICE";
+ private static final int TIMEOUT = 20_000;
+
+ private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ private final Context mContext = mInstrumentation.getContext();
+
+ @Test
+ public void testOnShownCallback() throws Exception {
+ try {
+ // setup
+ mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
+ PERMISSION_ACCESS_VOICE_INTERACTION_SERVICE);
+
+ // execution
+ SessionShowCallbackHelperImpl callbackHelperImpl = new SessionShowCallbackHelperImpl();
+ AssistUtilsHelper.showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+ callbackHelperImpl.waitForCallbackLatch();
+
+ // assertion
+ assertTrue(callbackHelperImpl.isSessionOnShown());
+ } finally {
+ mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+ }
+ }
+
+ private static class SessionShowCallbackHelperImpl implements
+ AssistUtilsHelper.VoiceInteractionSessionShowCallbackHelper {
+
+ private final CountDownLatch mCallbackLatch = new CountDownLatch(1);
+ private boolean mIsSessionOnShown = false;
+
+ public void onShown() {
+ mIsSessionOnShown = true;
+ Log.d(TAG, "onShown is called");
+ mCallbackLatch.countDown();
+ }
+
+ public void onFailed() {
+ Log.d(TAG, "onFailed");
+ }
+
+ private boolean isSessionOnShown() {
+ return mIsSessionOnShown;
+ }
+
+ private void waitForCallbackLatch() throws Exception {
+ mCallbackLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/AtomicFileHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/AtomicFileHelperTest.java
new file mode 100644
index 0000000..1add30c
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/AtomicFileHelperTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.car.builtin.util.AtomicFileHelper;
+import android.content.Context;
+import android.util.AtomicFile;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+
+@RunWith(AndroidJUnit4.class)
+public final class AtomicFileHelperTest {
+ private static final String TAG = AtomicFileHelperTest.class.getSimpleName();
+
+ private static final String PRE_EXIST_TEST_FILE_NAME = "TestFilePreExist";
+ private static final String NEWLY_CREATED_TEST_FILE_NAME = "TestFileNewlyCreated";
+ private static final String FAILED_TEST_FILE_NAME = "TestFileFailed";
+ private static final String REWRITE_TEST_FILE_NAME = "TestFileRewriteCreated";
+ private static final String TEST_FILE_CONTENT = "AtomicFileHelper CTS TEST FILE CONTENT";
+ private static final String REWRITE_CONTENT = "CTS TEST ATOMIC FILE REWRITE";
+
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+ @Test
+ public void testAtomicFileWithPreExistedBaseFile() throws Exception {
+ File appPrivateFileDir = mContext.getFilesDir();
+
+ // create a private file
+ mContext.openFileOutput(PRE_EXIST_TEST_FILE_NAME, Context.MODE_PRIVATE);
+
+ // create a File object to point to the newly created private file
+ File baseFile = new File(/* parent= */appPrivateFileDir, PRE_EXIST_TEST_FILE_NAME);
+ AtomicFile atomicFile = new AtomicFile(baseFile);
+
+ // execution and assertion
+ assertTrue(AtomicFileHelper.exists(atomicFile));
+ baseFile.delete();
+ assertFalse(AtomicFileHelper.exists(atomicFile));
+ }
+
+ @Test
+ public void testAtomicFileWithNonExistedBaseFile() throws Exception {
+ // setup
+ File appPrivateFileDir = mContext.getFilesDir();
+ File baseFile = new File(/* parent= */appPrivateFileDir, NEWLY_CREATED_TEST_FILE_NAME);
+ if (baseFile.exists()) {
+ baseFile.delete();
+ }
+ AtomicFile atomicFile = new AtomicFile(baseFile);
+
+ // execution and assertion
+ // 1. the base file does not exist yet
+ assertFalse(AtomicFileHelper.exists(atomicFile));
+
+ // 2. write into the atomic file and result a new base file
+ FileOutputStream fos = atomicFile.startWrite();
+ fos.write(TEST_FILE_CONTENT.getBytes());
+ atomicFile.finishWrite(fos);
+ assertTrue(AtomicFileHelper.exists(atomicFile));
+
+ // 3. check if the content match by directly read from the base file
+ BufferedReader br = new BufferedReader(new FileReader(baseFile));
+ String fileContent = br.readLine();
+ Log.d(TAG, fileContent);
+ assertEquals(TEST_FILE_CONTENT, fileContent);
+ br.close();
+
+ // 4. delete the base file;
+ baseFile.delete();
+ assertFalse(AtomicFileHelper.exists(atomicFile));
+ }
+
+ @Test
+ public void testAtomicFileWithFailedWrite() throws Exception {
+ // setup
+ File appPrivateFileDir = mContext.getFilesDir();
+ File baseFile = new File(/* parent= */appPrivateFileDir, REWRITE_TEST_FILE_NAME);
+ if (baseFile.exists()) {
+ baseFile.delete();
+ }
+ AtomicFile atomicFile = new AtomicFile(baseFile);
+
+ // execution and assertion
+ // 1. the base file does not exist yet
+ assertFalse(AtomicFileHelper.exists(atomicFile));
+
+ // 2. write into the atomic file and result a new base file
+ FileOutputStream fos = atomicFile.startWrite();
+ fos.write(TEST_FILE_CONTENT.getBytes());
+ atomicFile.failWrite(fos);
+ assertFalse(AtomicFileHelper.exists(atomicFile));
+ }
+
+ @Test
+ public void testAtomicFileRewrite() throws Exception {
+ // setup
+ File appPrivateFileDir = mContext.getFilesDir();
+ File baseFile = new File(/* parent= */appPrivateFileDir, NEWLY_CREATED_TEST_FILE_NAME);
+ if (baseFile.exists()) {
+ baseFile.delete();
+ }
+
+ // execution and assertion
+ // 1. write into the atomic file and result a new base file
+ AtomicFile atomicFile1 = new AtomicFile(baseFile);
+ FileOutputStream fos1 = atomicFile1.startWrite();
+ fos1.write(TEST_FILE_CONTENT.getBytes());
+ atomicFile1.finishWrite(fos1);
+ assertTrue(AtomicFileHelper.exists(atomicFile1));
+ fos1.close();
+
+ // 2. create a new AtomicFile and rewrite new content
+ AtomicFile atomicFile2 = new AtomicFile(baseFile);
+ FileOutputStream fos2 = atomicFile2.startWrite();
+ fos2.write(REWRITE_CONTENT.getBytes());
+ atomicFile2.finishWrite(fos2);
+ assertTrue(AtomicFileHelper.exists(atomicFile1));
+ fos2.close();
+
+ // 3. reopen the atomic file and read out the content
+ AtomicFile atomicFile3 = new AtomicFile(baseFile);
+ FileInputStream fis = atomicFile3.openRead();
+ String atomicFileReadContent = new String(atomicFile3.readFully());
+ Log.d(TAG, atomicFileReadContent);
+ assertEquals(atomicFileReadContent, REWRITE_CONTENT);
+ fis.close();
+
+ // 4. test if AtomicFile read is the same as base file read
+ BufferedReader br = new BufferedReader(new FileReader(baseFile));
+ String baseFileReadContent = br.readLine();
+ Log.d(TAG, baseFileReadContent);
+ assertEquals(atomicFileReadContent, baseFileReadContent);
+ br.close();
+
+ // 5. delete the base file;
+ baseFile.delete();
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/EventLogHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/EventLogHelperTest.java
new file mode 100644
index 0000000..ba34cb3
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/EventLogHelperTest.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.util;
+
+import android.car.builtin.util.EventLogHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public final class EventLogHelperTest {
+
+ private static final int TIMEOUT_MS = 60_000;
+ // All Eventlogs would be logged to event buffer.
+ private static final LogcatHelper.Buffer BUFFER = LogcatHelper.Buffer.EVENTS;
+
+ @Before
+ public void setup() {
+ LogcatHelper.clearLog();
+ }
+
+ @Test
+ public void testWriteCarHelperStart() {
+ EventLogHelper.writeCarHelperStart();
+
+ assertLogMessage("I car_helper_start:");
+ }
+
+ @Test
+ public void testWriteCarHelperBootPhase() {
+ EventLogHelper.writeCarHelperBootPhase(1);
+
+ assertLogMessage("I car_helper_boot_phase: 1");
+ }
+
+ @Test
+ public void testWriteCarHelperUserStarting() {
+ EventLogHelper.writeCarHelperUserStarting(100);
+
+ assertLogMessage("I car_helper_user_starting: 100");
+ }
+
+ @Test
+ public void testWriteCarHelperUserSwitching() {
+ EventLogHelper.writeCarHelperUserSwitching(100, 101);
+
+ assertLogMessage("I car_helper_user_switching: [100,101]");
+ }
+
+ @Test
+ public void testWriteCarHelperUserUnlocking() {
+ EventLogHelper.writeCarHelperUserUnlocking(100);
+
+ assertLogMessage("I car_helper_user_unlocking: 100");
+ }
+
+ @Test
+ public void testWriteCarHelperUserUnlocked() {
+ EventLogHelper.writeCarHelperUserUnlocked(100);
+
+ assertLogMessage("I car_helper_user_unlocked: 100");
+ }
+
+ @Test
+ public void testWriteCarHelperUserStopping() {
+ EventLogHelper.writeCarHelperUserStopping(100);
+
+ assertLogMessage("I car_helper_user_stopping: 100");
+ }
+
+ @Test
+ public void testWriteCarHelperUserStopped() {
+ EventLogHelper.writeCarHelperUserStopped(100);
+
+ assertLogMessage("I car_helper_user_stopped: 100");
+ }
+
+ @Test
+ public void testWriteCarHelperServiceConnected() {
+ EventLogHelper.writeCarHelperServiceConnected();
+
+ assertLogMessage("I car_helper_svc_connected");
+ }
+
+ @Test
+ public void testWriteCarServiceInit() {
+ EventLogHelper.writeCarServiceInit(101);
+
+ assertLogMessage("I car_service_init: 101");
+ }
+
+ @Test
+ public void testWriteCarServiceVhalReconnected() {
+ EventLogHelper.writeCarServiceVhalReconnected(101);
+
+ assertLogMessage("I car_service_vhal_reconnected: 101");
+ }
+
+ @Test
+ public void testWriteCarServiceSetCarServiceHelper() {
+ EventLogHelper.writeCarServiceSetCarServiceHelper(101);
+
+ assertLogMessage("I car_service_set_car_service_helper: 101");
+ }
+
+ @Test
+ public void tesWriteCarServiceOnUserLifecycle() {
+ EventLogHelper.writeCarServiceOnUserLifecycle(1, 2, 3);
+
+ assertLogMessage("I car_service_on_user_lifecycle: [1,2,3]");
+ }
+
+ @Test
+ public void testWriteCarServiceCreate() {
+ EventLogHelper.writeCarServiceCreate(true);
+
+ assertLogMessage("I car_service_create: 1");
+ }
+
+ @Test
+ public void testWriteCarServiceConnected() {
+ EventLogHelper.writeCarServiceConnected("testString");
+
+ assertLogMessage("I car_service_connected: testString");
+ }
+
+ @Test
+ public void testWriteCarServiceDestroy() {
+ EventLogHelper.writeCarServiceDestroy(true);
+
+ assertLogMessage("I car_service_destroy: 1");
+ }
+
+ @Test
+ public void testWriteCarServiceVhalDied() {
+ EventLogHelper.writeCarServiceVhalDied(101);
+
+ assertLogMessage("I car_service_vhal_died: 101");
+ }
+
+ @Test
+ public void testWriteCarServiceInitBootUser() {
+ EventLogHelper.writeCarServiceInitBootUser();
+
+ assertLogMessage("I car_service_init_boot_user");
+ }
+
+ @Test
+ public void testWriteCarServiceOnUserRemoved() {
+ EventLogHelper.writeCarServiceOnUserRemoved(101);
+
+ assertLogMessage("I car_service_on_user_removed: 101");
+ }
+
+ @Test
+ public void testWriteCarUserServiceInitialUserInfoReq() {
+ EventLogHelper.writeCarUserServiceInitialUserInfoReq(1, 2, 3, 4, 5);
+
+ assertLogMessage("I car_user_svc_initial_user_info_req: [1,2,3,4,5]");
+ }
+
+ @Test
+ public void testWriteCarUserServiceInitialUserInfoResp() {
+ EventLogHelper.writeCarUserServiceInitialUserInfoResp(1, 2, 3, 4, "string1", "string2");
+
+ assertLogMessage("I car_user_svc_initial_user_info_resp: [1,2,3,4,string1,string2]");
+ }
+
+ @Test
+ public void testWriteCarUserServiceSetInitialUser() {
+ EventLogHelper.writeCarUserServiceSetInitialUser(101);
+
+ assertLogMessage("I car_user_svc_set_initial_user: 101");
+ }
+
+ @Test
+ public void testWriteCarUserServiceSetLifecycleListener() {
+ EventLogHelper.writeCarUserServiceSetLifecycleListener(101, "string1");
+
+ assertLogMessage("I car_user_svc_set_lifecycle_listener: [101,string1]");
+ }
+
+ @Test
+ public void testWriteCarUserServiceResetLifecycleListener() {
+ EventLogHelper.writeCarUserServiceResetLifecycleListener(101, "string1");
+
+ assertLogMessage("I car_user_svc_reset_lifecycle_listener: [101,string1]");
+ }
+
+ @Test
+ public void testWriteCarUserServiceSwitchUserReq() {
+ EventLogHelper.writeCarUserServiceSwitchUserReq(101, 102);
+
+ assertLogMessage("I car_user_svc_switch_user_req: [101,102]");
+ }
+
+ @Test
+ public void testWriteCarUserServiceSwitchUserResp() {
+ EventLogHelper.writeCarUserServiceSwitchUserResp(101, 102, "string");
+
+ assertLogMessage("I car_user_svc_switch_user_resp: [101,102,string]");
+ }
+
+ @Test
+ public void testWriteCarUserServicePostSwitchUserReq() {
+ EventLogHelper.writeCarUserServicePostSwitchUserReq(101, 102);
+
+ assertLogMessage("I car_user_svc_post_switch_user_req: [101,102]");
+ }
+
+ @Test
+ public void testWriteCarUserServiceGetUserAuthReq() {
+ EventLogHelper.writeCarUserServiceGetUserAuthReq(101, 102, 103);
+
+ assertLogMessage("I car_user_svc_get_user_auth_req: [101,102,103]");
+ }
+
+ @Test
+ public void testWriteCarUserServiceGetUserAuthResp() {
+ EventLogHelper.writeCarUserServiceGetUserAuthResp(101);
+
+ assertLogMessage("I car_user_svc_get_user_auth_resp: 101");
+ }
+
+ @Test
+ public void testWriteCarUserServiceSwitchUserUiReq() {
+ EventLogHelper.writeCarUserServiceSwitchUserUiReq(101);
+
+ assertLogMessage("I car_user_svc_switch_user_ui_req: 101");
+ }
+
+ @Test
+ public void testWriteCarUserServiceSwitchUserFromHalReq() {
+ EventLogHelper.writeCarUserServiceSwitchUserFromHalReq(101, 102);
+
+ assertLogMessage("I car_user_svc_switch_user_from_hal_req: [101,102]");
+ }
+
+ @Test
+ public void testWriteCarUserServiceSetUserAuthReq() {
+ EventLogHelper.writeCarUserServiceSetUserAuthReq(101, 102, 103);
+
+ assertLogMessage("I car_user_svc_set_user_auth_req: [101,102,103]");
+ }
+
+ @Test
+ public void testWriteCarUserServiceSetUserAuthResp() {
+ EventLogHelper.writeCarUserServiceSetUserAuthResp(101, "string");
+
+ assertLogMessage("I car_user_svc_set_user_auth_resp: [101,string]");
+ }
+
+ @Test
+ public void testWriteCarUserServiceCreateUserReq() {
+ EventLogHelper.writeCarUserServiceCreateUserReq("string1", "string2", 101, 102, 103);
+
+ assertLogMessage("I car_user_svc_create_user_req: [string1,string2,101,102,103]");
+ }
+
+ @Test
+ public void testWriteCarUserServiceCreateUserResp() {
+ EventLogHelper.writeCarUserServiceCreateUserResp(101, 102, "string");
+
+ assertLogMessage("I car_user_svc_create_user_resp: [101,102,string]");
+ }
+
+ @Test
+ public void testWriteCarUserServiceCreateUserUserCreated() {
+ EventLogHelper.writeCarUserServiceCreateUserUserCreated(101, "string1", "string2", 102);
+
+ assertLogMessage("I car_user_svc_create_user_user_created: [101,string1,string2,102]");
+ }
+
+ @Test
+ public void testWriteCarUserServiceCreateUserUserRemoved() {
+ EventLogHelper.writeCarUserServiceCreateUserUserRemoved(101, "string");
+
+ assertLogMessage("I car_user_svc_create_user_user_removed: [101,string]");
+ }
+
+ @Test
+ public void testWriteCarUserServiceRemoveUserReq() {
+ EventLogHelper.writeCarUserServiceRemoveUserReq(101, 102);
+
+ assertLogMessage("I car_user_svc_remove_user_req: [101,102]");
+ }
+
+ private void assertLogMessage(String match) {
+ LogcatHelper.assertLogcatMessage(match, BUFFER, TIMEOUT_MS);
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/LogcatHelper.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/LogcatHelper.java
new file mode 100644
index 0000000..0648f56
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/LogcatHelper.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.util;
+
+import static org.junit.Assert.fail;
+
+import android.app.UiAutomation;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.BufferedReader;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+public final class LogcatHelper {
+
+ private LogcatHelper() {}
+
+ /**
+ * Logcat buffers to search.
+ */
+ public enum Buffer{
+ EVENTS, MAIN, SYSTEM, ALL;
+ }
+
+ /**
+ * Asserts if a message appears in logcat messages within given timeout. All logcat buffers are
+ * searched.
+ *
+ * @param match to find in the logcat messages
+ * @param timeout for waiting the message
+ */
+ public static void assertLogcatMessage(String match, int timeout) {
+ assertLogcatMessage(match, Buffer.ALL, timeout);
+ }
+
+ /**
+ * Asserts if a message appears in logcat messages within given timeout in the given buffer.
+ *
+ * @param match to find in the logcat messages
+ * @param buffer is logcat buffer to search
+ * @param timeout for waiting the message
+ */
+ public static void assertLogcatMessage(String match, Buffer buffer, int timeout) {
+ long startTime = SystemClock.elapsedRealtime();
+ UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ ParcelFileDescriptor output = automation
+ .executeShellCommand("logcat -b " + buffer.name().toLowerCase());
+ FileDescriptor fd = output.getFileDescriptor();
+ FileInputStream fileInputStream = new FileInputStream(fd);
+ try (BufferedReader bufferedReader = new BufferedReader(
+ new InputStreamReader(fileInputStream))) {
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ if (line.contains(match)) {
+ return;
+ }
+ if ((SystemClock.elapsedRealtime() - startTime) > timeout) {
+ fail("match" + match + " was not found, Timeout: " + timeout + " ms");
+ }
+ }
+ } catch (IOException e) {
+ fail("match was not found, IO exception: " + e);
+ }
+ }
+
+ /**
+ * Asserts if a message does not appears in logcat messages within given timeout. If the message
+ * appears, then assertion will fail.
+ *
+ * @param match to find in the logcat messages
+ * @param timeout for waiting the message
+ */
+ public static void assertNoLogcatMessage(String match, int timeout) throws Exception {
+ long startTime = SystemClock.elapsedRealtime();
+ UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ ParcelFileDescriptor output = automation.executeShellCommand("logcat -b all");
+ FileDescriptor fd = output.getFileDescriptor();
+ FileInputStream fileInputStream = new FileInputStream(fd);
+ try (BufferedReader bufferedReader = new BufferedReader(
+ new InputStreamReader(fileInputStream))) {
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ if (line.contains(match)) {
+ fail("Match was not expected, but found: " + match);
+ }
+ if ((SystemClock.elapsedRealtime() - startTime) > timeout) {
+ return;
+ }
+ }
+ } catch (IOException e) {
+ fail("match was not found, IO exception: " + e);
+ }
+ }
+
+ /**
+ * Clears all logs.
+ */
+ public static void clearLog() {
+ SystemUtil.runShellCommand("logcat -b all -c");
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/SlogfTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/SlogfTest.java
new file mode 100644
index 0000000..896da38
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/SlogfTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.util;
+
+import static android.car.cts.builtin.util.SlogfTest.Level.DEBUG;
+import static android.car.cts.builtin.util.SlogfTest.Level.ERROR;
+import static android.car.cts.builtin.util.SlogfTest.Level.VERBOSE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.builtin.util.Slogf;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public final class SlogfTest {
+ private static final String TAG = SlogfTest.class.getSimpleName();
+ private static final int TIMEOUT_MS = 60_000;
+ // All Slogf would be logged to system buffer.
+ private static final LogcatHelper.Buffer BUFFER = LogcatHelper.Buffer.SYSTEM;
+ // wait time for waiting to make sure msg is not logged. Should not be a high value as tests
+ // waits for this much time.
+ private static final int NOT_LOGGED_WAIT_TIME_MS = 5_000;
+ private static final String LOGCAT_LINE_FORMAT = "%s %s: %s";
+
+ private static final String LOGGED_MSG = "This message should exist in logcat.";
+ private static final String NOT_LOGGED_MSG = "This message should not exist in logcat.";
+ private static final String FORMATTED_MSG = "This message is a format with two args %s and %s.";
+ private static final String EXCEPTION_MSG = "This message should exist in logcat.";
+
+ enum Level {
+ VERBOSE("V"), DEBUG("D"), ERROR("E");
+
+ private String mValue;
+
+ public String getValue() {
+ return mValue;
+ }
+
+ Level(String v) {
+ mValue = v;
+ }
+ }
+
+ @Before
+ public void setup() {
+ setLogLevel(VERBOSE);
+ clearLog();
+ }
+
+ @After
+ public void reset() {
+ setLogLevel(VERBOSE);
+ clearLog();
+ }
+
+ @Test
+ public void testV_msg1() {
+ Slogf.v(TAG, LOGGED_MSG);
+
+ assertLogcatMessage(VERBOSE, LOGGED_MSG);
+ }
+
+ @Test
+ public void testV_msg2() throws Exception {
+ Throwable throwable = new Throwable(EXCEPTION_MSG);
+
+ Slogf.v(TAG, LOGGED_MSG, throwable);
+
+ assertLogcatMessage(VERBOSE, LOGGED_MSG);
+ assertLogcatMessage(VERBOSE, "java.lang.Throwable: " + EXCEPTION_MSG);
+ assertLogcatStackTrace(VERBOSE, throwable);
+
+ }
+
+ @Test
+ public void testV_msg3() {
+ Slogf.v(TAG, FORMATTED_MSG, "input1", "input2");
+
+ assertLogcatMessage(VERBOSE, String.format(FORMATTED_MSG, "input1", "input2"));
+ }
+
+ @Test
+ public void testV_noMsg1() throws Exception {
+ setLogLevel(ERROR);
+
+ Slogf.v(TAG, NOT_LOGGED_MSG);
+
+ assertNoLogcatMessage(VERBOSE, NOT_LOGGED_MSG);
+ }
+
+ @Test
+ public void testV_noMsg2() throws Exception {
+ setLogLevel(ERROR);
+
+ Slogf.v(TAG, FORMATTED_MSG, "input1", "input2");
+
+ assertNoLogcatMessage(VERBOSE, String.format(FORMATTED_MSG, "input1", "input2"));
+ }
+
+ @Test
+ public void testD_msg1() {
+ Slogf.d(TAG, LOGGED_MSG);
+
+ assertLogcatMessage(DEBUG, LOGGED_MSG);
+ }
+
+ @Test
+ public void testD_msg2() {
+ Throwable throwable = new Throwable(EXCEPTION_MSG);
+ Slogf.d(TAG, LOGGED_MSG, throwable);
+
+ assertLogcatMessage(DEBUG, LOGGED_MSG);
+ assertLogcatMessage(DEBUG, "java.lang.Throwable: " + EXCEPTION_MSG);
+ assertLogcatStackTrace(DEBUG, throwable);
+ }
+
+ @Test
+ public void testD_msg3() {
+ Slogf.d(TAG, FORMATTED_MSG, "input1", "input2");
+
+ assertLogcatMessage(DEBUG, String.format(FORMATTED_MSG, "input1", "input2"));
+ }
+
+ @Test
+ public void testD_noMsg1() throws Exception {
+ setLogLevel(ERROR);
+
+ Slogf.d(TAG, NOT_LOGGED_MSG);
+
+ assertNoLogcatMessage(DEBUG, NOT_LOGGED_MSG);
+ }
+
+ @Test
+ public void testD_noMsg2() throws Exception {
+ setLogLevel(ERROR);
+
+ Slogf.d(TAG, FORMATTED_MSG, "input1", "input2");
+
+ assertNoLogcatMessage(DEBUG, String.format(FORMATTED_MSG, "input1", "input2"));
+ }
+
+ @Test
+ public void testIsLoggableTrue() throws Exception {
+ setLogLevel(VERBOSE);
+
+ assertThat(Slogf.isLoggable(TAG, Log.VERBOSE)).isTrue();
+ }
+
+ @Test
+ public void testIsLoggableFalse() throws Exception {
+ setLogLevel(ERROR);
+
+ assertThat(Slogf.isLoggable(TAG, Log.VERBOSE)).isFalse();
+ }
+
+ private void clearLog() {
+ LogcatHelper.clearLog();
+ }
+
+ private void setLogLevel(Level level) {
+ SystemUtil.runShellCommand("setprop log.tag.SlogfTest " + level.getValue());
+ }
+
+ private void assertNoLogcatMessage(Level level, String msg) throws Exception {
+ String match = String.format(LOGCAT_LINE_FORMAT, level.getValue(), TAG, msg);
+ LogcatHelper.assertNoLogcatMessage(match, NOT_LOGGED_WAIT_TIME_MS);
+ }
+
+ private void assertLogcatMessage(Level level, String msg) {
+ String match = String.format(LOGCAT_LINE_FORMAT, level.getValue(), TAG, msg);
+ LogcatHelper.assertLogcatMessage(match, BUFFER, TIMEOUT_MS);
+ }
+
+ private void assertLogcatStackTrace(Level level, Throwable throwable) {
+ StackTraceElement[] elements = throwable.getStackTrace();
+ for (int i = 0; i < elements.length; i++) {
+ assertLogcatMessage(level, "\tat " + elements[i]);
+ }
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/TimeUtilsTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/TimeUtilsTest.java
new file mode 100644
index 0000000..92393d3
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/TimeUtilsTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.util;
+
+import android.car.builtin.util.TimeUtils;
+
+import org.junit.Test;
+
+import java.io.PrintWriter;
+
+public final class TimeUtilsTest {
+ private PrintWriter mWriter = new PrintWriter(System.out);
+
+ private static final int TIMEOUT_MS = 60_000;
+
+ @Test
+ public void testDumpTime() {
+ TimeUtils.dumpTime(mWriter, 179);
+ mWriter.flush();
+
+ // Time utils change long into date-time format.
+ LogcatHelper.assertLogcatMessage("System.out: 1970-01-01 00:00:00.179", TIMEOUT_MS);
+ }
+
+ @Test
+ public void testFormatDuration() {
+ TimeUtils.formatDuration(789, mWriter);
+ mWriter.flush();
+
+ // Time utils change long into human readable text.
+ LogcatHelper.assertLogcatMessage("System.out: +789ms", TIMEOUT_MS);
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/TimingsTraceLogTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/TimingsTraceLogTest.java
new file mode 100644
index 0000000..0fec391
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/TimingsTraceLogTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.util;
+
+import static android.car.cts.builtin.util.LogcatHelper.assertLogcatMessage;
+
+import android.car.builtin.os.TraceHelper;
+import android.car.builtin.util.TimingsTraceLog;
+
+import org.junit.Test;
+
+public final class TimingsTraceLogTest {
+
+ private static final String TAG = TimingsTraceLogTest.class.getSimpleName();
+ private static final int TIMEOUT_MS = 60_000;
+
+ @Test
+ public void testTimingsTraceLog() {
+ TimingsTraceLog timingsTraceLog =
+ new TimingsTraceLog(TAG, TraceHelper.TRACE_TAG_CAR_SERVICE);
+ timingsTraceLog.traceBegin("testTimingsTraceLog");
+ timingsTraceLog.traceEnd();
+
+ assertLogcatMessage("TimingsTraceLogTest: testTimingsTraceLog took to complete",
+ TIMEOUT_MS);
+ }
+
+ @Test
+ public void testTimingsTraceLogDuration() {
+ TimingsTraceLog timingsTraceLog =
+ new TimingsTraceLog(TAG, TraceHelper.TRACE_TAG_CAR_SERVICE);
+ timingsTraceLog.logDuration("testTimingsTraceLogDuration", 159);
+
+ assertLogcatMessage(
+ "TimingsTraceLogTest: testTimingsTraceLogDuration took to complete: 159ms",
+ TIMEOUT_MS);
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/ValidationHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/ValidationHelperTest.java
new file mode 100644
index 0000000..7fb1da3
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/ValidationHelperTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.car.builtin.util.ValidationHelper;
+import android.os.Process;
+import android.os.UserHandle;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class ValidationHelperTest {
+ // constants from android.os.UserHandle
+ private static final int USER_NULL = -10000;
+ private static final int USER_CURRENT_OR_SELF = -3;
+ private static final int USER_CURRENT = -2;
+ private static final int USER_ALL = -1;
+ private static final int USER_SYSTEM = 0;
+ private static final int PER_USER_RANGE = 100000;
+
+ @Test
+ public void testUserIdValidation() {
+ // setup
+ int maxUserId = Integer.MAX_VALUE / PER_USER_RANGE;
+ int invalidUserId = maxUserId + 1;
+
+ // assert pre-defined user ids
+ assertTrue(ValidationHelper.isUserIdValid(USER_NULL));
+ assertTrue(ValidationHelper.isUserIdValid(USER_CURRENT_OR_SELF));
+ assertTrue(ValidationHelper.isUserIdValid(USER_CURRENT));
+ assertTrue(ValidationHelper.isUserIdValid(USER_ALL));
+ assertTrue(ValidationHelper.isUserIdValid(USER_SYSTEM));
+ assertTrue(ValidationHelper.isUserIdValid(maxUserId));
+
+ // assert dynamical user ids
+ assertTrue(ValidationHelper.isUserIdValid(UserHandle.myUserId()));
+
+ // assert boundary conditions
+ assertFalse(ValidationHelper.isUserIdValid(invalidUserId));
+ }
+
+ @Test
+ public void testAppIdValidation() {
+ // setup
+ int outOfRangeAppId = PER_USER_RANGE + 1;
+
+ // assert pre-defined app ids
+ assertTrue(ValidationHelper.isAppIdValid(Process.FIRST_APPLICATION_UID));
+ assertTrue(ValidationHelper.isAppIdValid(Process.LAST_APPLICATION_UID));
+ assertTrue(ValidationHelper.isAppIdValid(Process.ROOT_UID));
+ assertTrue(ValidationHelper.isAppIdValid(Process.SYSTEM_UID));
+ assertTrue(ValidationHelper.isAppIdValid(Process.SHELL_UID));
+
+ // assert dynamical app ids
+ assertTrue(ValidationHelper.isAppIdValid(Process.myUid() % PER_USER_RANGE));
+ assertTrue(ValidationHelper.isAppIdValid(Process.FIRST_APPLICATION_UID + 1));
+ assertTrue(ValidationHelper.isAppIdValid(Process.LAST_APPLICATION_UID - 1));
+
+ // assert boundary conditions
+ assertFalse(ValidationHelper.isAppIdValid(Process.INVALID_UID));
+ assertFalse(ValidationHelper.isAppIdValid(outOfRangeAppId));
+ }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/view/DisplayHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/view/DisplayHelperTest.java
new file mode 100644
index 0000000..5ac7527
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/view/DisplayHelperTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.builtin.view;
+
+import static android.car.cts.builtin.app.DisplayUtils.VirtualDisplaySession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.car.builtin.view.DisplayHelper;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
+import android.util.Log;
+import android.view.Display;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+public final class DisplayHelperTest {
+
+ private static final String TAG = DisplayHelperTest.class.getSimpleName();
+
+ // the constant comes from com.android.server.display.LocalDisplayAdapter.UNIQUE_ID_PREFIX
+ private static final String DISPLAY_ID_PREFIX_LOCAL = "local:";
+ // the constant comes from com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX
+ private static final String DISPLAY_ID_PREFIX_VIRTUAL = "virtual:";
+
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+ @Test
+ public void testLocalDisplayPhysicalPort() throws Exception {
+ // setup
+ ArrayList<Display> allLocalDisplays = getAllLocalDisplays();
+
+ // assert that there is at least one local display
+ assertThat(allLocalDisplays.size()).isGreaterThan(0);
+
+ // execution and assertion
+ for (Display d : allLocalDisplays) {
+ int physicalPort = DisplayHelper.getPhysicalPort(d);
+ Log.d(TAG, "Display Physical Port: " + physicalPort);
+ assertThat(physicalPort).isNotEqualTo(DisplayHelper.INVALID_PORT);
+
+ String uniqueId = DisplayHelper.getUniqueId(d);
+ assertThat(physicalPort).isEqualTo(getPhysicalPortFromId(uniqueId));
+ }
+ }
+
+ @Test
+ public void testVirtualDisplayPhysicalPort() throws Exception {
+ // check the assumption
+ String requiredFeature = PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(requiredFeature));
+
+ try (VirtualDisplaySession session = new VirtualDisplaySession()) {
+ Display vDisplay =
+ session.createDisplayWithDefaultDisplayMetricsAndWait(mContext, true);
+ assertThat(DisplayHelper.getPhysicalPort(vDisplay))
+ .isEqualTo(DisplayHelper.INVALID_PORT);
+ }
+ }
+
+ private ArrayList<Display> getAllLocalDisplays() {
+ DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ Display[] allDisplays = displayManager.getDisplays();
+
+ ArrayList<Display> localDisplays = new ArrayList<>();
+ for (Display d : allDisplays) {
+ if (isDisplayLocal(d)) {
+ localDisplays.add(d);
+ }
+ }
+
+ return localDisplays;
+ }
+
+ private boolean isDisplayLocal(Display display) {
+ String uniqueId = DisplayHelper.getUniqueId(display);
+ return uniqueId.startsWith(DISPLAY_ID_PREFIX_LOCAL);
+ }
+
+ private int getPhysicalPortFromId(String displayUniqueId) throws Exception {
+ if (displayUniqueId == null) {
+ throw new IllegalArgumentException("null displayId string");
+ }
+
+ int startIndex = DISPLAY_ID_PREFIX_LOCAL.length();
+ long physicalDisplayId = Long.parseLong(displayUniqueId.substring(startIndex));
+ return (int) (physicalDisplayId & 0xFF);
+ }
+}
diff --git a/tests/tests/companion/Android.bp b/tests/tests/companion/Android.bp
new file mode 100644
index 0000000..22a45e8
--- /dev/null
+++ b/tests/tests/companion/Android.bp
@@ -0,0 +1,111 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+ name: "cts-companion-common",
+ srcs: [
+ "common/src/**/*.kt",
+ ],
+ manifest: "common/AndroidManifest.xml",
+
+ platform_apis: true,
+ static_libs: [
+ "androidx.test.ext.junit",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "junit",
+ "kotlin-test",
+ ],
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ // TODO(b/211264986) Change both SDK versions to T (33) once it's formally defined.
+ min_sdk_version: "current",
+ target_sdk_version: "current",
+}
+
+android_test {
+ name: "CtsCompanionDeviceManagerCoreTestCases",
+ srcs: [
+ "core/src/**/*.kt",
+ ],
+ manifest: "core/AndroidManifest.xml",
+ test_config: "core/AndroidTest.xml",
+
+ platform_apis: true,
+ static_libs: [
+ "androidx.test.ext.junit",
+ "compatibility-device-util-axt",
+ "cts-companion-common",
+ "ctstestrunner-axt",
+ "junit",
+ "kotlin-test",
+ ],
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ defaults: ["cts_defaults"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+
+ // TODO(b/211264986) Change both SDK versions to T (33) once it's formally defined.
+ min_sdk_version: "current",
+ target_sdk_version: "current",
+}
+
+android_test {
+ name: "CtsCompanionDeviceManagerUiAutomationTestCases",
+ srcs: [
+ "uiautomation/src/**/*.kt",
+ ],
+ manifest: "uiautomation/AndroidManifest.xml",
+ test_config: "uiautomation/AndroidTest.xml",
+
+ platform_apis: true,
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.uiautomator",
+ "compatibility-device-util-axt",
+ "cts-companion-common",
+ "ctstestrunner-axt",
+ "junit",
+ "kotlin-test",
+ ],
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ defaults: ["cts_defaults"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+
+ // TODO(b/211264986) Change both SDK versions to T (33) once it's formally defined.
+ min_sdk_version: "current",
+ target_sdk_version: "current",
+}
diff --git a/tests/tests/companion/OWNERS b/tests/tests/companion/OWNERS
new file mode 100644
index 0000000..23e30ab
--- /dev/null
+++ b/tests/tests/companion/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 708992
+evanxinchen@google.com
+ewol@google.com
+guojing@google.com
+sergeynv@google.com
diff --git a/tests/tests/companion/README.md b/tests/tests/companion/README.md
new file mode 100644
index 0000000..560f8f7
--- /dev/null
+++ b/tests/tests/companion/README.md
@@ -0,0 +1,14 @@
+CTS tests for `CompanionDeviceManager` are split into 2 modules:
+`CtsCompanionDeviceManagerCoreTestCases` (a.k.a. "Core Tests") and
+`CtsCompanionDeviceManagerUiAutomationTestCases` (a.k.a. "UiAutomation Tests").
+
+The core difference between the two test modules is that `CtsCompanionDeviceManager_Core_TestCases`
+does NOT use `UiAutomation` which makes it:
+- faster
+- suitable for to run on NFFs
+- less prone to flakiness
+- better suitable to run in `PRESUBMIT`.
+
+`CtsCompanionDeviceManager_UiAutomation_TestCases`, on the other hand, uses `UiAutomation` in order
+to test CDM flows end-to-end and is (at least for now) designed to run only on the mobile
+form-factor and requires a discoverable BT device nearby.
diff --git a/tests/tests/companion/common/AndroidManifest.xml b/tests/tests/companion/common/AndroidManifest.xml
new file mode 100644
index 0000000..6a9a3bc
--- /dev/null
+++ b/tests/tests/companion/common/AndroidManifest.xml
@@ -0,0 +1,59 @@
+<?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.companion.cts.common">
+
+ <uses-feature android:name="android.software.companion_device_setup" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <service
+ android:name=".PrimaryCompanionService"
+ android:exported="true"
+ android:label="Primary Companion Service"
+ android:permission="android.permission.BIND_COMPANION_DEVICE_SERVICE">
+ <intent-filter>
+ <action android:name="android.companion.CompanionDeviceService" />
+ </intent-filter>
+
+ <meta-data
+ android:name="android.companion.primary"
+ android:value="true" />
+ </service>
+
+ <service
+ android:name=".SecondaryCompanionService"
+ android:exported="true"
+ android:label="Secondary Companion Service"
+ android:permission="android.permission.BIND_COMPANION_DEVICE_SERVICE">
+ <intent-filter>
+ <action android:name="android.companion.CompanionDeviceService" />
+ </intent-filter>
+ </service>
+
+ <activity
+ android:name=".CompanionActivity"
+ android:exported="false"
+ android:label="Companion Activity"
+ android:launchMode="singleInstance"
+ android:excludeFromRecents="true"/>
+ </application>
+
+</manifest>
+
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/AppHelper.kt b/tests/tests/companion/common/src/android/companion/cts/common/AppHelper.kt
new file mode 100644
index 0000000..31fe4d6
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/AppHelper.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.common
+
+import android.app.Instrumentation
+import android.net.MacAddress
+import java.lang.UnsupportedOperationException
+
+/** Utility class for interacting with applications via Shell */
+class AppHelper(
+ private val instrumentation: Instrumentation,
+ val userId: Int,
+ val packageName: String,
+ private val apkPath: String? = null
+) {
+ fun associate(macAddress: MacAddress) =
+ runShellCommand("cmd companiondevice associate $userId $packageName $macAddress")
+
+ fun disassociate(macAddress: MacAddress) =
+ runShellCommand("cmd companiondevice disassociate $userId $packageName $macAddress")
+
+ fun isInstalled(): Boolean =
+ runShellCommand("pm list packages --user $userId $packageName").isNotBlank()
+
+ fun install() = apkPath?.let { runShellCommand("pm install --user $userId $apkPath") }
+ ?: throw UnsupportedOperationException("APK path is not provided.")
+
+ fun uninstall() = runShellCommand("pm uninstall --user $userId $packageName")
+
+ fun clearData() = runShellCommand("pm clear --user $userId $packageName")
+
+ fun addToHoldersOfRole(role: String) =
+ runShellCommand("cmd role add-role-holder --user $userId $role $packageName")
+
+ fun removeFromHoldersOfRole(role: String) =
+ runShellCommand("cmd role remove-role-holder --user $userId $role $packageName")
+
+ fun withRole(role: String, block: () -> Unit) {
+ addToHoldersOfRole(role)
+ try {
+ block()
+ } finally {
+ removeFromHoldersOfRole(role)
+ }
+ }
+
+ private fun runShellCommand(cmd: String) = instrumentation.runShellCommand(cmd)
+}
\ No newline at end of file
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/CompanionActivity.kt b/tests/tests/companion/common/src/android/companion/cts/common/CompanionActivity.kt
new file mode 100644
index 0000000..833e718
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/CompanionActivity.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.common
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.content.IntentSender
+import android.os.Bundle
+import android.util.Log
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+
+/**
+ * An [Activity] for launching confirmation UI via an [android.content.IntentSender.sendIntent] and
+ * receiving result in [onActivityResult].
+ */
+class CompanionActivity : Activity() {
+ private var result: Pair<Int, Intent?>? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ Log.d(TAG, "$this.onCreate()")
+ super.onCreate(savedInstanceState)
+ unsafeInstance = this
+ }
+
+ override fun onDestroy() {
+ Log.d(TAG, "$this.onDestroy()")
+ unsafeInstance = null
+ super.onDestroy()
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ Log.i(TAG, "onActivityResult() code=${resultCode.codeToString()}, " +
+ "data=${intent.extras}")
+ result = resultCode to data
+ }
+
+ companion object {
+ private var unsafeInstance: CompanionActivity? = null
+ val instance: CompanionActivity
+ get() = unsafeInstance ?: error("There is no CompanionActivity")
+
+ fun launchAndWait(context: Context) {
+ val intent = Intent(context, CompanionActivity::class.java)
+ intent.addFlags(FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(intent)
+
+ waitForResult(timeout = 3.seconds, interval = 100.milliseconds) {
+ unsafeInstance?.takeIf { it.isResumed }
+ } ?: error("CompanionActivity has not appeared")
+ }
+
+ fun startIntentSender(intentSender: IntentSender) =
+ instance.startIntentSenderForResult(intentSender, 0, null, 0, 0, 0)
+
+ fun waitForActivityResult() =
+ waitForResult(timeout = 1.seconds, interval = 100.milliseconds) { instance.result }
+ ?: error("onActivityResult() has not been invoked")
+
+ fun finish() = instance.finish()
+
+ fun safeFinish() = unsafeInstance?.finish()
+
+ fun waitUntilGone() = waitFor { unsafeInstance == null }
+ }
+}
+
+private fun Int.codeToString() = when (this) {
+ Activity.RESULT_OK -> "RESULT_OK"
+ Activity.RESULT_CANCELED -> "RESULT_CANCELED"
+ else -> "Unknown"
+} + "($this)"
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/CompanionService.kt b/tests/tests/companion/common/src/android/companion/cts/common/CompanionService.kt
new file mode 100644
index 0000000..4c16478
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/CompanionService.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.common
+
+import android.companion.AssociationInfo
+import android.companion.CompanionDeviceService
+import android.content.Intent
+import android.os.Handler
+import android.util.Log
+import java.util.Collections.synchronizedMap
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+
+sealed class CompanionService<T : CompanionService<T>>(
+ private val instanceHolder: InstanceHolder<T>
+) : CompanionDeviceService() {
+ @Volatile var isBound: Boolean = false
+ private set(isBound) {
+ Log.d(TAG, "$this.isBound=$isBound")
+ if (!isBound && !connectedDevices.isEmpty())
+ error("Unbinding while there are connected devices")
+ field = isBound
+ }
+
+ val connectedDevices: Collection<AssociationInfo>
+ get() = _connectedDevices.values
+
+ val associationIdsForConnectedDevices: Collection<Int>
+ get() = _connectedDevices.keys
+
+ private val _connectedDevices: MutableMap<Int, AssociationInfo> =
+ synchronizedMap(mutableMapOf())
+
+ override fun onCreate() {
+ Log.d(TAG, "$this.onCreate()")
+ super.onCreate()
+ instanceHolder.instance = this as T
+ }
+
+ override fun onBindCompanionDeviceService(intent: Intent) {
+ Log.d(TAG, "$this.onBindCompanionDeviceService()")
+ isBound = true
+ }
+
+ override fun onDeviceAppeared(associationInfo: AssociationInfo) {
+ Log.d(TAG, "$this.onDevice_Appeared(), association=$associationInfo")
+ _connectedDevices[associationInfo.id] = associationInfo
+ super.onDeviceAppeared(associationInfo)
+ }
+
+ override fun onDeviceDisappeared(associationInfo: AssociationInfo) {
+ Log.d(TAG, "$this.onDevice_Disappeared(), association=$associationInfo")
+ _connectedDevices.remove(associationInfo.id)
+ ?: error("onDeviceAppeared() has not been called for association with id " +
+ "${associationInfo.id}")
+
+ super.onDeviceDisappeared(associationInfo)
+ }
+
+ // For now, we need to "post" a Runnable that sets isBound to false to the Main Thread's
+ // Handler, because this may be called between invocations of
+ // CompanionDeviceService.Stub.onDeviceAppeared() and the "real"
+ // CompanionDeviceService.onDeviceAppeared(), which would cause an error() in isBound setter.
+ override fun onUnbind(intent: Intent?) = super.onUnbind(intent)
+ .also {
+ Log.d(TAG, "$this.onUnbind()")
+ Handler.getMain().post { isBound = false }
+ }
+
+ override fun onDestroy() {
+ Log.d(TAG, "$this.onDestroy()")
+ instanceHolder.instance = null
+ super.onDestroy()
+ }
+}
+
+sealed class InstanceHolder<T : CompanionService<T>> {
+ // Need synchronization, because the setter will be called from the Main thread, while the
+ // getter is expected to be called mostly from the instrumentation thread.
+ var instance: T? = null
+ @Synchronized internal set
+ @Synchronized get
+
+ val isBound: Boolean
+ get() = instance?.isBound ?: false
+
+ val connectedDevices: Collection<AssociationInfo>
+ get() = instance?.connectedDevices ?: emptySet()
+
+ val associationIdsForConnectedDevices: Collection<Int>
+ get() = instance?.associationIdsForConnectedDevices ?: emptySet()
+
+ fun waitForBind(timeout: Duration = 1.seconds) {
+ if (!waitFor(timeout) { isBound })
+ throw AssertionError("Service hasn't been bound")
+ }
+
+ fun waitForUnbind(timeout: Duration) {
+ if (!waitFor(timeout) { !isBound })
+ throw AssertionError("Service hasn't been unbound")
+ }
+
+ fun waitAssociationToAppear(associationId: Int, timeout: Duration = 1.seconds) {
+ val appeared = waitFor(timeout) {
+ associationIdsForConnectedDevices.contains(associationId)
+ }
+ if (!appeared) throw AssertionError("""Association with $associationId hasn't "appeared"""")
+ }
+
+ fun waitAssociationToDisappear(associationId: Int, timeout: Duration = 1.seconds) {
+ val gone = waitFor(timeout) {
+ !associationIdsForConnectedDevices.contains(associationId)
+ }
+ if (!gone) throw AssertionError("""Association with $associationId hasn't "disappeared"""")
+ }
+}
+
+class PrimaryCompanionService : CompanionService<PrimaryCompanionService>(Companion) {
+ companion object : InstanceHolder<PrimaryCompanionService>()
+}
+
+class SecondaryCompanionService : CompanionService<SecondaryCompanionService>(Companion) {
+ companion object : InstanceHolder<SecondaryCompanionService>()
+}
\ No newline at end of file
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/Constants.kt b/tests/tests/companion/common/src/android/companion/cts/common/Constants.kt
new file mode 100644
index 0000000..c7471d1
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/Constants.kt
@@ -0,0 +1,53 @@
+package android.companion.cts.common
+
+import android.Manifest
+import android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING
+import android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION
+import android.companion.AssociationRequest.DEVICE_PROFILE_WATCH
+import android.net.MacAddress
+import android.os.Handler
+import android.os.HandlerThread
+import java.util.concurrent.Executor
+
+/** Set of all supported CDM Device Profiles. */
+val DEVICE_PROFILES = setOf(
+ DEVICE_PROFILE_WATCH,
+ DEVICE_PROFILE_APP_STREAMING,
+ DEVICE_PROFILE_AUTOMOTIVE_PROJECTION
+)
+
+val DEVICE_PROFILE_TO_NAME = mapOf(
+ DEVICE_PROFILE_WATCH to "WATCH",
+ DEVICE_PROFILE_APP_STREAMING to "APP_STREAMING",
+ DEVICE_PROFILE_AUTOMOTIVE_PROJECTION to "AUTOMOTIVE_PROJECTION"
+)
+
+val DEVICE_PROFILE_TO_PERMISSION = mapOf(
+ DEVICE_PROFILE_WATCH to Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH,
+ DEVICE_PROFILE_APP_STREAMING to
+ Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING,
+ DEVICE_PROFILE_AUTOMOTIVE_PROJECTION to
+ Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION
+)
+
+val MAC_ADDRESS_A = MacAddress.fromString("00:00:00:00:00:AA")
+val MAC_ADDRESS_B = MacAddress.fromString("00:00:00:00:00:BB")
+val MAC_ADDRESS_C = MacAddress.fromString("00:00:00:00:00:CC")
+
+const val DEVICE_DISPLAY_NAME_A = "Device A"
+const val DEVICE_DISPLAY_NAME_B = "Device B"
+
+val SIMPLE_EXECUTOR: Executor by lazy { Executor { it.run() } }
+
+val MAIN_THREAD_EXECUTOR: Executor by lazy {
+ Executor {
+ with(Handler.getMain()) { post(it) }
+ }
+}
+
+val BACKGROUND_THREAD_EXECUTOR: Executor by lazy {
+ with(HandlerThread("CdmTestBackgroundThread")) {
+ start()
+ Executor { threadHandler.post(it) }
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/InvocationTracker.kt b/tests/tests/companion/common/src/android/companion/cts/common/InvocationTracker.kt
new file mode 100644
index 0000000..4f7ed66
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/InvocationTracker.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.common
+
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+
+interface InvocationTracker<T> {
+ val invocations: List<T>
+
+ /**
+ * Await invocations of this callback by the given [actions].
+ */
+ fun assertInvokedByActions(
+ timeout: Duration = 1.seconds,
+ minOccurrences: Int = 1,
+ actions: () -> Unit
+ ) {
+ require(minOccurrences > 0) {
+ "Must expect at least one callback occurrence. (Given $minOccurrences)"
+ }
+ val expectedInvocationCount = invocations.size + minOccurrences
+ actions()
+ if (!waitFor(timeout, interval = 100.milliseconds) {
+ invocations.size >= expectedInvocationCount
+ }) {
+ throw AssertionError(
+ "Callback was invoked ${invocations.size} times after $timeout ms! " +
+ "Expected at least $minOccurrences times."
+ )
+ }
+ }
+
+ fun clearRecordedInvocations()
+
+ fun recordInvocation(invocation: T)
+}
+
+internal class InvocationContainer<T> : InvocationTracker<T> {
+ private val _invocations: MutableList<T> = mutableListOf()
+ override val invocations: List<T>
+ @Synchronized
+ get() = _invocations
+
+ @Synchronized
+ override fun clearRecordedInvocations() = _invocations.clear()
+
+ @Synchronized
+ override fun recordInvocation(invocation: T) {
+ _invocations.add(invocation)
+ }
+}
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/RecordingCallback.kt b/tests/tests/companion/common/src/android/companion/cts/common/RecordingCallback.kt
new file mode 100644
index 0000000..52a4610
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/RecordingCallback.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.common
+
+import android.companion.AssociationInfo
+import android.companion.CompanionDeviceManager
+import android.content.IntentSender
+import android.util.Log
+
+class RecordingCallback
+private constructor(container: InvocationContainer<CallbackInvocation>) :
+ CompanionDeviceManager.Callback(),
+ InvocationTracker<RecordingCallback.CallbackInvocation> by container {
+
+ constructor() : this(InvocationContainer())
+
+ override fun onDeviceFound(intentSender: IntentSender) =
+ logAndRecordInvocation(OnDeviceFound(intentSender))
+
+ override fun onAssociationPending(intentSender: IntentSender) =
+ logAndRecordInvocation(OnAssociationPending(intentSender))
+
+ override fun onAssociationCreated(associationInfo: AssociationInfo) =
+ logAndRecordInvocation(OnAssociationCreated(associationInfo))
+
+ override fun onFailure(error: CharSequence?) = logAndRecordInvocation(OnFailure(error))
+
+ private fun logAndRecordInvocation(invocation: CallbackInvocation) {
+ Log.d(TAG, "Callback: $invocation")
+ recordInvocation(invocation)
+ }
+
+ sealed interface CallbackInvocation
+ data class OnDeviceFound(val intentSender: IntentSender) : CallbackInvocation
+ data class OnAssociationPending(val intentSender: IntentSender) : CallbackInvocation
+ data class OnAssociationCreated(val associationInfo: AssociationInfo) : CallbackInvocation
+ data class OnFailure(val error: CharSequence?) : CallbackInvocation
+}
\ No newline at end of file
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/RecordingCdmEventObserver.kt b/tests/tests/companion/common/src/android/companion/cts/common/RecordingCdmEventObserver.kt
new file mode 100644
index 0000000..7ba5552
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/RecordingCdmEventObserver.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.common
+
+import android.companion.AssociationInfo
+import android.companion.CompanionDeviceManager
+import android.companion.cts.common.RecordingCallback.CallbackInvocation
+import android.companion.cts.common.RecordingCallback.OnAssociationCreated
+import android.companion.cts.common.RecordingCallback.OnAssociationPending
+import android.companion.cts.common.RecordingCallback.OnDeviceFound
+import android.companion.cts.common.RecordingCallback.OnFailure
+import android.content.IntentSender
+
+/**
+ * This class is a combination of a
+ * [android.companion.CompanionDeviceManager.OnAssociationsChangedListener] and a
+ * [android.companion.CompanionDeviceManager.Callback].
+ *
+ * It can simultaneously serve as both an association change listener and a CDM callback,
+ * enabling it to make assertions on the order of all externally observable CDM events.
+ */
+class RecordingCdmEventObserver
+private constructor(container: InvocationContainer<CdmEvent>) :
+ CompanionDeviceManager.Callback(),
+ CompanionDeviceManager.OnAssociationsChangedListener,
+ InvocationTracker<RecordingCdmEventObserver.CdmEvent> by container {
+
+ constructor() : this(InvocationContainer())
+
+ // association change listener behavior
+
+ override fun onAssociationsChanged(associations: List<AssociationInfo>) =
+ recordInvocation(AssociationChange(associations))
+
+ // CDM callback behavior
+
+ override fun onDeviceFound(intentSender: IntentSender) =
+ recordInvocation(CdmCallback(OnDeviceFound(intentSender)))
+
+ override fun onAssociationPending(intentSender: IntentSender) =
+ recordInvocation(CdmCallback(OnAssociationPending(intentSender)))
+
+ override fun onAssociationCreated(associationInfo: AssociationInfo) =
+ recordInvocation(CdmCallback(OnAssociationCreated(associationInfo)))
+
+ override fun onFailure(error: CharSequence?) =
+ recordInvocation(CdmCallback(OnFailure(error)))
+
+ sealed interface CdmEvent
+ data class AssociationChange(val associations: List<AssociationInfo>) : CdmEvent
+ data class CdmCallback(val invocation: CallbackInvocation) : CdmEvent
+}
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/RecordingOnAssociationsChangedListener.kt b/tests/tests/companion/common/src/android/companion/cts/common/RecordingOnAssociationsChangedListener.kt
new file mode 100644
index 0000000..c024a79
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/RecordingOnAssociationsChangedListener.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.companion.cts.common
+
+import android.companion.AssociationInfo
+import android.companion.CompanionDeviceManager
+
+class RecordingOnAssociationsChangedListener
+private constructor(container: InvocationContainer<List<AssociationInfo>>) :
+ CompanionDeviceManager.OnAssociationsChangedListener,
+ InvocationTracker<List<AssociationInfo>> by container {
+
+ constructor() : this(InvocationContainer())
+
+ override fun onAssociationsChanged(associations: List<AssociationInfo>) =
+ recordInvocation(associations)
+}
\ No newline at end of file
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/Repeat.kt b/tests/tests/companion/common/src/android/companion/cts/common/Repeat.kt
new file mode 100644
index 0000000..c0370bf
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/Repeat.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.common
+
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Repeats the execution of the annotated test method the given number of times (10 by default).
+ *
+ * Note that a [RepeatRule] must be present in the class for the annotation to take effect.
+ *
+ * For an example, see the following code:
+ * ```
+ * @get:Rule
+ * val repeatRule = RepeatRule()
+ *
+ * @Test
+ * @Repeat(5)
+ * fun myTest() {
+ * ...
+ * }
+ * ```
+ */
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FUNCTION)
+annotation class Repeat(val times: Int = 10)
+
+/**
+ * Use this rule together with the [Repeat] annotation to automatically
+ * repeat the execution of annotated tests.
+ */
+class RepeatRule : TestRule {
+ override fun apply(base: Statement, description: Description): Statement =
+ description.getAnnotation(Repeat::class.java)?.let { repeat ->
+ object : Statement() {
+ @Throws(Throwable::class)
+ override fun evaluate() = repeat(repeat.times) { base.evaluate() }
+ }
+ } ?: base
+}
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/TestBase.kt b/tests/tests/companion/common/src/android/companion/cts/common/TestBase.kt
new file mode 100644
index 0000000..23aa32e
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/TestBase.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.common
+
+import android.annotation.CallSuper
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.companion.AssociationInfo
+import android.companion.CompanionDeviceManager
+import android.content.Context
+import android.content.pm.PackageManager
+import android.net.MacAddress
+import android.os.SystemClock.sleep
+import android.os.SystemClock.uptimeMillis
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.AssumptionViolatedException
+import org.junit.Before
+import java.io.IOException
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+
+/**
+ * A base class for CompanionDeviceManager [Tests][org.junit.Test] to extend.
+ */
+abstract class TestBase {
+ protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ protected val uiAutomation: UiAutomation = instrumentation.uiAutomation
+
+ protected val context: Context = instrumentation.context
+ protected val userId = context.userId
+ private val targetPackageName = instrumentation.targetContext.packageName
+
+ protected val targetApp = AppHelper(instrumentation, userId, targetPackageName)
+
+ protected val pm: PackageManager by lazy { context.packageManager!! }
+ private val hasCompanionDeviceSetupFeature by lazy {
+ pm.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)
+ }
+
+ protected val cdm: CompanionDeviceManager by lazy {
+ context.getSystemService(CompanionDeviceManager::class.java)!!
+ }
+
+ @Before
+ fun base_setUp() {
+ assumeTrue(hasCompanionDeviceSetupFeature)
+
+ // Remove all existing associations (for the user).
+ assertEmpty(withShellPermissionIdentity {
+ cdm.disassociateAll()
+ cdm.allAssociations
+ })
+
+ // Make sure CompanionDeviceServices are not bound.
+ assertFalse(PrimaryCompanionService.isBound)
+ assertFalse(SecondaryCompanionService.isBound)
+
+ setUp()
+ }
+
+ @After
+ fun base_tearDown() {
+ if (!hasCompanionDeviceSetupFeature) return
+
+ tearDown()
+
+ // Remove all existing associations (for the user).
+ withShellPermissionIdentity { cdm.disassociateAll() }
+ }
+
+ @CallSuper
+ protected open fun setUp() {}
+
+ @CallSuper
+ protected open fun tearDown() {}
+
+ protected fun <T> withShellPermissionIdentity(
+ vararg permissions: String,
+ block: () -> T
+ ): T {
+ if (permissions.isNotEmpty()) {
+ uiAutomation.adoptShellPermissionIdentity(*permissions)
+ } else {
+ uiAutomation.adoptShellPermissionIdentity()
+ }
+
+ try {
+ return block()
+ } finally {
+ uiAutomation.dropShellPermissionIdentity()
+ }
+ }
+
+ private fun CompanionDeviceManager.disassociateAll() =
+ allAssociations.forEach { disassociate(it.id) }
+}
+
+const val TAG = "CtsCompanionDeviceManagerTestCases"
+
+fun <T> assumeThat(message: String, obj: T, assumption: (T) -> Boolean) {
+ if (!assumption(obj)) throw AssumptionViolatedException(message)
+}
+
+fun <T> assertEmpty(list: Collection<T>) = assertTrue("Collection is not empty") { list.isEmpty() }
+
+fun assertAssociations(
+ actual: List<AssociationInfo>,
+ expected: Set<Pair<String, MacAddress?>>
+) = assertEquals(actual = actual.map { it.packageName to it.deviceMacAddress }.toSet(),
+ expected = expected)
+
+/**
+ * @return whether the condition was met before time ran out.
+ */
+fun waitFor(
+ timeout: Duration = 10.seconds,
+ interval: Duration = 1.seconds,
+ condition: () -> Boolean
+): Boolean {
+ val startTime = uptimeMillis()
+ while (!condition()) {
+ if (uptimeMillis() - startTime > timeout.inWholeMilliseconds) return false
+ sleep(interval.inWholeMilliseconds)
+ }
+ return true
+}
+
+fun <R> waitForResult(
+ timeout: Duration = 10.seconds,
+ interval: Duration = 1.seconds,
+ block: () -> R
+): R? {
+ val startTime = uptimeMillis()
+ while (true) {
+ val result: R = block()
+ if (result != null) return result
+ sleep(interval.inWholeMilliseconds)
+ if (uptimeMillis() - startTime > timeout.inWholeMilliseconds) return null
+ }
+}
+
+fun Instrumentation.runShellCommand(cmd: String): String {
+ Log.i(TAG, "Running shell command: '$cmd'")
+ try {
+ val out = SystemUtil.runShellCommand(this, cmd)
+ Log.i(TAG, "Out:\n$out")
+ return out
+ } catch (e: IOException) {
+ Log.e(TAG, "Error running shell command: $cmd")
+ throw e
+ }
+}
+
+fun Instrumentation.setSystemProp(name: String, value: String) =
+ runShellCommand("setprop $name $value")
\ No newline at end of file
diff --git a/tests/tests/companion/core/AndroidManifest.xml b/tests/tests/companion/core/AndroidManifest.xml
new file mode 100644
index 0000000..048558a
--- /dev/null
+++ b/tests/tests/companion/core/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.companion.cts.core">
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.companion.cts.core"
+ android:label="CompanionDeviceManager Core CTS tests">
+
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/companion/core/AndroidTest.xml b/tests/tests/companion/core/AndroidTest.xml
new file mode 100644
index 0000000..21d3dd63
--- /dev/null
+++ b/tests/tests/companion/core/AndroidTest.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="Configuration for Core CTS tests for CompanionDeviceManager">
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <option name="not-shardable" value="true" />
+ <option name="test-suite-tag" value="cts" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsCompanionDeviceManagerCoreTestCases.apk" />
+ <option name="test-file-name" value="CtsCompanionTestApp.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.companion.cts.core" />
+ <option name="runtime-hint" value="1s" />
+ </test>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Create a temporary directory for test APKs. -->
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts/companion" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts/companion" />
+
+ <option name="run-command" value="am wait-for-broadcast-idle" />
+ </target_preparer>
+
+ <!-- Push test APKs into the temporary directory. -->
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="push" value="CtsCompanionTestApp.apk->/data/local/tmp/cts/companion/CtsCompanionTestApp.apk" />
+ </target_preparer>
+</configuration>
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/AssociateSelfManagedTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/AssociateSelfManagedTest.kt
new file mode 100644
index 0000000..b7a70f2
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/AssociateSelfManagedTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.core
+
+import android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH
+import android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED
+import android.companion.AssociationRequest
+import android.companion.AssociationRequest.DEVICE_PROFILE_WATCH
+import android.companion.cts.common.RecordingCallback
+import android.companion.cts.common.RecordingCallback.OnAssociationCreated
+import android.companion.cts.common.SIMPLE_EXECUTOR
+import android.companion.cts.common.assertEmpty
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertIs
+import kotlin.test.assertNull
+
+/**
+ * Test CDM APIs for requesting establishing new associations.
+ *
+ * Run: atest CtsCompanionDeviceManagerCoreTestCases:AssociateSelfManagedTest
+ *
+ * @see android.companion.CompanionDeviceManager.associate
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class AssociateSelfManagedTest : CoreTestBase() {
+
+ @Test
+ fun test_associate_selfManaged_requiresPermission() {
+ val request: AssociationRequest = AssociationRequest.Builder()
+ .setSelfManaged(true)
+ .setDisplayName(DEVICE_DISPLAY_NAME)
+ .build()
+ val callback = RecordingCallback()
+
+ // Attempts to create a "self-managed" association without the MANAGE_COMPANION_DEVICES
+ // permission should lead to a SecurityException being thrown.
+ assertFailsWith(SecurityException::class) {
+ cdm.associate(request, SIMPLE_EXECUTOR, callback)
+ }
+ assertEmpty(callback.invocations)
+
+ // Same call with the MANAGE_COMPANION_DEVICES permissions should succeed.
+ withShellPermissionIdentity(REQUEST_COMPANION_SELF_MANAGED) {
+ cdm.associate(request, SIMPLE_EXECUTOR, callback)
+ }
+ }
+
+ @Test
+ fun test_associate_selfManaged_nullProfile_leadsToNoUiFlow() {
+ val request: AssociationRequest = AssociationRequest.Builder()
+ .setSelfManaged(true)
+ .setDisplayName(DEVICE_DISPLAY_NAME)
+ .build()
+ val callback = RecordingCallback()
+
+ callback.assertInvokedByActions {
+ withShellPermissionIdentity(REQUEST_COMPANION_SELF_MANAGED) {
+ cdm.associate(request, SIMPLE_EXECUTOR, callback)
+ }
+ }
+
+ // Check callback invocations: there should have been exactly 1 invocation of the
+ // onAssociationCreated() method.
+ assertEquals(1, callback.invocations.size)
+ val associationInvocation = callback.invocations.first()
+ assertIs<OnAssociationCreated>(associationInvocation)
+ with(associationInvocation.associationInfo) {
+ assertEquals(actual = displayName, expected = DEVICE_DISPLAY_NAME)
+ assertNull(deviceProfile)
+ }
+
+ // Check that the newly created association is included in
+ // CompanionDeviceManager.getMyAssociations()
+ assertContentEquals(
+ actual = cdm.myAssociations,
+ expected = listOf(associationInvocation.associationInfo)
+ )
+ }
+
+ @Test
+ fun test_associate_selfManaged_alreadyRoleHolder_leadsToNoUiFlow() =
+ targetApp.withRole(ROLE_WATCH) {
+ val request: AssociationRequest = AssociationRequest.Builder()
+ .setSelfManaged(true)
+ .setDeviceProfile(DEVICE_PROFILE_WATCH)
+ .setDisplayName(DEVICE_DISPLAY_NAME)
+ .build()
+ val callback = RecordingCallback()
+
+ callback.assertInvokedByActions {
+ withShellPermissionIdentity(
+ REQUEST_COMPANION_SELF_MANAGED,
+ REQUEST_COMPANION_PROFILE_WATCH
+ ) {
+ cdm.associate(request, SIMPLE_EXECUTOR, callback)
+ }
+ }
+
+ // Check callback invocations: there should have been exactly 1 invocation of the
+ // onAssociationCreated().
+ assertEquals(1, callback.invocations.size)
+ val associationInvocation = callback.invocations.first()
+ assertIs<OnAssociationCreated>(associationInvocation)
+ with(associationInvocation.associationInfo) {
+ assertEquals(actual = displayName, expected = DEVICE_DISPLAY_NAME)
+ assertEquals(actual = deviceProfile, expected = ROLE_WATCH)
+ }
+
+ // Check that the newly created association is included in
+ // CompanionDeviceManager.getMyAssociations()
+ assertContentEquals(
+ actual = cdm.myAssociations,
+ expected = listOf(associationInvocation.associationInfo)
+ )
+ }
+
+ override fun tearDown() {
+ targetApp.removeFromHoldersOfRole(ROLE_WATCH)
+ super.tearDown()
+ }
+
+ companion object {
+ private const val DEVICE_DISPLAY_NAME = "My device"
+ private const val ROLE_WATCH = DEVICE_PROFILE_WATCH
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/AssociateTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/AssociateTest.kt
new file mode 100644
index 0000000..8e0e90d
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/AssociateTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.core
+
+import android.companion.AssociationRequest
+import android.companion.cts.common.RecordingCallback
+import android.companion.cts.common.RecordingCallback.OnAssociationPending
+import android.companion.cts.common.SIMPLE_EXECUTOR
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+
+/**
+ * Test CDM APIs for requesting establishing new associations.
+ *
+ * Run: atest CtsCompanionDeviceManagerCoreTestCases:AssociateTest
+ *
+ * @see android.companion.CompanionDeviceManager.associate
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class AssociateTest : CoreTestBase() {
+
+ @Test
+ fun test_associate() {
+ val request: AssociationRequest = AssociationRequest.Builder()
+ .build()
+ val callback = RecordingCallback()
+
+ callback.assertInvokedByActions {
+ cdm.associate(request, SIMPLE_EXECUTOR, callback)
+ }
+ // Check callback invocations: there should have been exactly 1 invocation of the
+ // onAssociationPending() method.
+ assertEquals(1, callback.invocations.size)
+ assertIs<OnAssociationPending>(callback.invocations.first())
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/AssociationRequestBuilderTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/AssociationRequestBuilderTest.kt
new file mode 100644
index 0000000..e999c86
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/AssociationRequestBuilderTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.core
+
+import android.companion.AssociationRequest
+import android.companion.AssociationRequest.DEVICE_PROFILE_WATCH
+import android.companion.BluetoothDeviceFilter
+import android.companion.cts.common.assertEmpty
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+/**
+ * Test [android.companion.AssociationRequest.Builder].
+ *
+ * Run: atest CtsCompanionDeviceManagerCoreTestCases:AssociationRequestBuilderTest
+ */
+@RunWith(AndroidJUnit4::class)
+class AssociationRequestBuilderTest {
+
+ @Test
+ fun test_defaultValues() {
+ val request = AssociationRequest.Builder()
+ .build()
+
+ request.apply {
+ assertNull(deviceProfile)
+ assertNull(displayName)
+
+ assertNotNull(deviceFilters)
+ assertEmpty(deviceFilters)
+
+ assertFalse(isSelfManaged)
+ assertFalse(isForceConfirmation)
+ assertFalse(isSingleDevice)
+ }
+ }
+
+ @Test
+ fun test_setters() {
+ val deviceFilterA = createBluetoothDeviceFilter("00:00:00:00:00:AA")
+ val deviceFilterB = createBluetoothDeviceFilter("00:00:00:00:00:BB")
+ val request = AssociationRequest.Builder()
+ .setDeviceProfile(DEVICE_PROFILE_WATCH)
+ .setDisplayName(DISPLAY_NAME)
+ .setSelfManaged(true)
+ .setForceConfirmation(true)
+ .setSingleDevice(true)
+ .addDeviceFilter(deviceFilterA)
+ .addDeviceFilter(deviceFilterB)
+ .build()
+
+ request.apply {
+ assertEquals(actual = deviceProfile, expected = DEVICE_PROFILE_WATCH)
+ assertEquals(actual = displayName, expected = DISPLAY_NAME)
+ assertContentEquals(
+ actual = deviceFilters,
+ expected = listOf(deviceFilterA, deviceFilterB))
+ assertTrue(isSelfManaged)
+ assertTrue(isForceConfirmation)
+ assertTrue(isSingleDevice)
+ }
+ }
+
+ @Test
+ fun test_selfManaged_require_displayName() {
+ assertFailsWith<IllegalStateException> {
+ AssociationRequest.Builder()
+ .setSelfManaged(true)
+ .build()
+ }
+ }
+
+ companion object {
+ private const val DISPLAY_NAME = "My Device"
+ }
+}
+
+private fun createBluetoothDeviceFilter(address: String) = BluetoothDeviceFilter.Builder()
+ .setAddress(address)
+ .build()
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/AssociationsChangedListenerTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/AssociationsChangedListenerTest.kt
new file mode 100644
index 0000000..a523a62
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/AssociationsChangedListenerTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.core
+
+import android.Manifest.permission.MANAGE_COMPANION_DEVICES
+import android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED
+import android.companion.AssociationRequest
+import android.companion.cts.common.BACKGROUND_THREAD_EXECUTOR
+import android.companion.cts.common.DEVICE_DISPLAY_NAME_A
+import android.companion.cts.common.MAC_ADDRESS_A
+import android.companion.cts.common.RecordingCallback.OnAssociationCreated
+import android.companion.cts.common.RecordingCdmEventObserver
+import android.companion.cts.common.RecordingCdmEventObserver.AssociationChange
+import android.companion.cts.common.RecordingCdmEventObserver.CdmCallback
+import android.companion.cts.common.RecordingOnAssociationsChangedListener
+import android.companion.cts.common.Repeat
+import android.companion.cts.common.RepeatRule
+import android.companion.cts.common.SIMPLE_EXECUTOR
+import android.companion.cts.common.assertEmpty
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertIs
+
+/**
+ * Test CDM APIs for listening for changes to [android.companion.AssociationInfo].
+ *
+ * Run: atest CtsCompanionDeviceManagerCoreTestCases:AssociationsChangedListenerTest
+ *
+ * @see android.companion.CompanionDeviceManager.OnAssociationsChangedListener
+ * @see android.companion.CompanionDeviceManager.addOnAssociationsChangedListener
+ * @see android.companion.CompanionDeviceManager.removeOnAssociationsChangedListener
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class AssociationsChangedListenerTest : CoreTestBase() {
+ @get:Rule
+ val repeatRule = RepeatRule()
+
+ @Test
+ fun test_addOnAssociationsChangedListener_requiresPermission() {
+ /**
+ * Attempts to add a listener without [MANAGE_COMPANION_DEVICES] permission should
+ * throw a [SecurityException] and should not change the existing associations.
+ */
+ assertFailsWith(SecurityException::class) {
+ cdm.addOnAssociationsChangedListener(SIMPLE_EXECUTOR, NO_OP_LISTENER)
+ }
+
+ /** Re-running with [MANAGE_COMPANION_DEVICES] permissions: now should succeed */
+ withShellPermissionIdentity(MANAGE_COMPANION_DEVICES) {
+ cdm.addOnAssociationsChangedListener(SIMPLE_EXECUTOR, NO_OP_LISTENER)
+
+ /** Succeeded, now remove. */
+ cdm.removeOnAssociationsChangedListener(NO_OP_LISTENER)
+ }
+ }
+
+ @Test
+ fun test_addOnAssociationsChangedListener() {
+ val listener = RecordingOnAssociationsChangedListener()
+
+ withShellPermissionIdentity(MANAGE_COMPANION_DEVICES) {
+ cdm.addOnAssociationsChangedListener(SIMPLE_EXECUTOR, listener)
+ }
+
+ listener.assertInvokedByActions {
+ testApp.associate(MAC_ADDRESS_A)
+ }
+
+ listener.invocations[0].let { associations ->
+ assertEquals(actual = associations.size, expected = 1)
+ assertEquals(actual = associations[0].deviceMacAddress, expected = MAC_ADDRESS_A)
+ assertEquals(actual = associations[0].packageName, expected = TEST_APP_PACKAGE_NAME)
+ }
+
+ listener.clearRecordedInvocations()
+
+ withShellPermissionIdentity(MANAGE_COMPANION_DEVICES) {
+ cdm.removeOnAssociationsChangedListener(listener)
+ }
+
+ testApp.disassociate(MAC_ADDRESS_A)
+ // The listener shouldn't get involved after removed the onAssociationsChangedListener.
+ assertEmpty(listener.invocations)
+ }
+
+ @Test
+ @Repeat(10)
+ fun test_associationChangeListener_notifiedBefore_cdmCallback() {
+ val request: AssociationRequest = AssociationRequest.Builder()
+ .setSelfManaged(true)
+ .setDisplayName(DEVICE_DISPLAY_NAME_A)
+ .build()
+
+ val observer = RecordingCdmEventObserver()
+
+ // preparation: register the observer as an association change listener
+ withShellPermissionIdentity(MANAGE_COMPANION_DEVICES) {
+ cdm.addOnAssociationsChangedListener(BACKGROUND_THREAD_EXECUTOR, observer)
+ }
+
+ // test scenario: carry out an association and assert that
+ // the association listener is notified BEFORE the CDM observer
+ observer.assertInvokedByActions(minOccurrences = 2) {
+ withShellPermissionIdentity(REQUEST_COMPANION_SELF_MANAGED) {
+ // in order to make sure the OnAssociationsChangedListener and
+ // CompanionDeviceManager.Callback callbacks are recorded in the right order use
+ // the same Executor - BACKGROUND_THREAD_EXECUTOR - here that we used for
+ // addOnAssociationsChangedListener above.
+ cdm.associate(request, BACKGROUND_THREAD_EXECUTOR, observer)
+ }
+ }
+
+ // we should have observed exactly two events
+ assertEquals(2, observer.invocations.size)
+ val (event1, event2) = observer.invocations
+
+ // the event we observed first should be an association change
+ assertIs<AssociationChange>(event1)
+ // there should be exactly one association
+ assertEquals(1, event1.associations.size)
+ val associationInfoFromListener = event1.associations.first()
+ assertEquals(
+ actual = associationInfoFromListener.displayName,
+ expected = DEVICE_DISPLAY_NAME_A
+ )
+
+ // the second event should be the callback invocation
+ assertIs<CdmCallback>(event2)
+ val callbackInvocation = event2.invocation
+ assertIs<OnAssociationCreated>(callbackInvocation)
+
+ val associationInfoFromCallback = callbackInvocation.associationInfo
+ assertEquals(associationInfoFromListener, associationInfoFromCallback)
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/AssociationsCleanUpTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/AssociationsCleanUpTest.kt
new file mode 100644
index 0000000..332816a
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/AssociationsCleanUpTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.core
+
+import android.annotation.UserIdInt
+import android.companion.AssociationInfo
+import android.companion.CompanionDeviceManager
+import android.companion.cts.common.AppHelper
+import android.companion.cts.common.MAC_ADDRESS_A
+import android.companion.cts.common.MAC_ADDRESS_B
+import android.companion.cts.common.MAC_ADDRESS_C
+import android.companion.cts.common.assertAssociations
+import android.companion.cts.common.waitFor
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests if associations that belong to a package are removed when the package is uninstalled or its
+ * data is cleared.
+ *
+ * Build/Install/Run: atest CtsCompanionDeviceManagerCoreTestCases:AssociationsCleanUpTest
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class AssociationsCleanUpTest : CoreTestBase() {
+
+ @Test
+ fun test_associationsRemoved_onPackageDataCleared() {
+ testApp.associate(MAC_ADDRESS_A)
+ testApp.associate(MAC_ADDRESS_B)
+ targetApp.associate(MAC_ADDRESS_C)
+
+ assertAssociations(
+ actual = withShellPermissionIdentity { cdm.allAssociations },
+ expected = setOf(
+ testApp.packageName to MAC_ADDRESS_A,
+ testApp.packageName to MAC_ADDRESS_B,
+ targetApp.packageName to MAC_ADDRESS_C
+ ))
+
+ /** Clear test app's data. */
+ testApp.clearData()
+ assertAssociationsRemovedFor(testApp)
+
+ /** Only Test App's associations should have been removed. Others - should remain. */
+ assertAssociations(
+ actual = withShellPermissionIdentity { cdm.allAssociations },
+ expected = setOf(
+ targetApp.packageName to MAC_ADDRESS_C
+ ))
+ }
+
+ @Test
+ fun test_associationsRemoved_onPackageRemoved() {
+ testApp.associate(MAC_ADDRESS_A)
+ testApp.associate(MAC_ADDRESS_B)
+ targetApp.associate(MAC_ADDRESS_C)
+
+ assertAssociations(
+ actual = withShellPermissionIdentity { cdm.allAssociations },
+ expected = setOf(
+ testApp.packageName to MAC_ADDRESS_A,
+ testApp.packageName to MAC_ADDRESS_B,
+ targetApp.packageName to MAC_ADDRESS_C
+ ))
+
+ /** Uninstall test app. */
+ testApp.uninstall()
+ assertAssociationsRemovedFor(testApp)
+
+ /** Only Test App's associations should have been removed. Others - should remain. */
+ assertAssociations(
+ actual = withShellPermissionIdentity { cdm.allAssociations },
+ expected = setOf(
+ targetApp.packageName to MAC_ADDRESS_C
+ ))
+ }
+
+ private fun assertAssociationsRemovedFor(app: AppHelper) = waitFor {
+ withShellPermissionIdentity {
+ cdm.getAssociationForPackage(app.userId, app.packageName).isEmpty()
+ }
+ }.let { removed ->
+ if (!removed)
+ throw AssertionError("Associations for ${app.packageName} were not removed.")
+ }
+}
+
+private fun CompanionDeviceManager.getAssociationForPackage(
+ @UserIdInt userId: Int,
+ packageName: String
+): List<AssociationInfo> = allAssociations.filter { it.belongsToPackage(userId, packageName) }
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/BasicTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/BasicTest.kt
new file mode 100644
index 0000000..198c4ad
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/BasicTest.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.core
+
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertNotNull
+
+/**
+ * Tests most basic CDM APis.
+ *
+ * Build/Install/Run: atest CtsCompanionDeviceManagerCoreTestCases:BasicTest
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class BasicTest : CoreTestBase() {
+ @Test
+ fun test_manager_isNotNull() {
+ assertNotNull(cdm)
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/Constants.kt b/tests/tests/companion/core/src/android/companion/cts/core/Constants.kt
new file mode 100644
index 0000000..a25ee93
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/Constants.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.core
+
+const val TEST_APP_PACKAGE_NAME = "android.os.cts.companiontestapp"
+const val TEST_APP_APK_PATH = "/data/local/tmp/cts/companion/CtsCompanionTestApp.apk"
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/CoreTestBase.kt b/tests/tests/companion/core/src/android/companion/cts/core/CoreTestBase.kt
new file mode 100644
index 0000000..07795fd
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/CoreTestBase.kt
@@ -0,0 +1,31 @@
+package android.companion.cts.core
+
+import android.annotation.CallSuper
+import android.companion.CompanionDeviceManager
+import android.companion.cts.common.AppHelper
+import android.companion.cts.common.TestBase
+import kotlin.test.assertTrue
+
+open class CoreTestBase : TestBase() {
+ protected val testApp = AppHelper(
+ instrumentation, userId, TEST_APP_PACKAGE_NAME, TEST_APP_APK_PATH)
+
+ @CallSuper
+ override fun setUp() {
+ super.setUp()
+
+ // Make sure test app is installed.
+ with(testApp) {
+ if (!isInstalled()) install()
+ assertTrue("Test app $packageName is not installed") { isInstalled() }
+ }
+ }
+
+ protected val NO_OP_LISTENER: CompanionDeviceManager.OnAssociationsChangedListener =
+ CompanionDeviceManager.OnAssociationsChangedListener { }
+
+ protected val NO_OP_CALLBACK: CompanionDeviceManager.Callback =
+ object : CompanionDeviceManager.Callback() {
+ override fun onFailure(error: CharSequence?) = Unit
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/DeviceProfilesTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/DeviceProfilesTest.kt
new file mode 100644
index 0000000..fd14987
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/DeviceProfilesTest.kt
@@ -0,0 +1,125 @@
+package android.companion.cts.core
+
+import android.app.role.RoleManager.ROLE_ASSISTANT
+import android.app.role.RoleManager.ROLE_BROWSER
+import android.app.role.RoleManager.ROLE_CALL_REDIRECTION
+import android.app.role.RoleManager.ROLE_CALL_SCREENING
+import android.app.role.RoleManager.ROLE_DIALER
+import android.app.role.RoleManager.ROLE_EMERGENCY
+import android.app.role.RoleManager.ROLE_HOME
+import android.app.role.RoleManager.ROLE_SMS
+import android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION
+import android.app.role.RoleManager.ROLE_SYSTEM_WELLBEING
+import android.companion.AssociationRequest
+import android.companion.cts.common.DEVICE_PROFILE_TO_PERMISSION
+import android.companion.cts.common.RecordingCallback
+import android.companion.cts.common.RecordingCallback.OnAssociationPending
+import android.companion.cts.common.SIMPLE_EXECUTOR
+import android.companion.cts.common.assertEmpty
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertIs
+import kotlin.test.assertNotNull
+
+/**
+ * Test CDM device profiles.
+ *
+ * Run: atest CtsCompanionDeviceManagerCoreTestCases:DeviceProfilesTest
+ *
+ * @see android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING
+ * @see android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION
+ * @see android.companion.AssociationRequest.DEVICE_PROFILE_WATCH
+ * @see android.companion.CompanionDeviceManager.associate
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class DeviceProfilesTest : CoreTestBase() {
+ /** Test that all supported device profiles require a permission. */
+ @Test
+ fun test_supportedProfiles() {
+ val callback = RecordingCallback()
+ DEVICE_PROFILE_TO_PERMISSION.forEach { (profile, permission) ->
+ callback.clearRecordedInvocations()
+ val request = buildRequest(deviceProfile = profile)
+
+ // Should fail if called without the required permissions.
+ assertFailsWith(SecurityException::class) {
+ cdm.associate(request, SIMPLE_EXECUTOR, callback)
+ }
+ // Make sure callback wasn't invoked.
+ assertEmpty(callback.invocations)
+
+ // Should succeed when called with the required permission.
+ assertNotNull(permission, "Profile should require a permission")
+ // Associate and wait for callback.
+ callback.assertInvokedByActions {
+ withShellPermissionIdentity(permission) {
+ cdm.associate(request, SIMPLE_EXECUTOR, callback)
+ }
+ }
+ // Make sure it's the right callback.
+ assertEquals(1, callback.invocations.size)
+ assertIs<OnAssociationPending>(callback.invocations.first())
+ }
+ }
+
+ /** Test that CDM rejects "arbitrary" profiles. */
+ @Test
+ fun test_unsupportedProfiles() = withShellPermissionIdentity {
+ val callback = RecordingCallback()
+ UNSUPPORTED_PROFILES.forEach { profile ->
+ val request = buildRequest(deviceProfile = profile)
+ // Should fail if called without the required permissions.
+ assertFailsWith(IllegalArgumentException::class) {
+ cdm.associate(request, SIMPLE_EXECUTOR, callback)
+ }
+ // Make sure callback wasn't invoked.
+ assertEmpty(callback.invocations)
+ }
+ }
+
+ /** Test that the null profile is supported and does not require a permission. */
+ @Test
+ fun test_nullProfile() {
+ val callback = RecordingCallback()
+ val request = buildRequest(deviceProfile = null)
+
+ callback.assertInvokedByActions {
+ // Should not require a permission.
+ cdm.associate(request, SIMPLE_EXECUTOR, callback)
+ }
+ // Make sure it's the right callback.
+ assertEquals(1, callback.invocations.size)
+ assertIs<OnAssociationPending>(callback.invocations.first())
+ }
+
+ private fun buildRequest(deviceProfile: String?) = AssociationRequest.Builder()
+ .apply { deviceProfile?.let { setDeviceProfile(it) } }
+ .build()
+
+ companion object {
+ val UNSUPPORTED_PROFILES = setOf(
+ // Each supported device profile is backed by a role. However, other roles should
+ // not be treated as device profiles.
+ ROLE_ASSISTANT,
+ ROLE_BROWSER,
+ ROLE_DIALER,
+ ROLE_SMS,
+ ROLE_EMERGENCY,
+ ROLE_HOME,
+ ROLE_CALL_REDIRECTION,
+ ROLE_CALL_SCREENING,
+ ROLE_SYSTEM_WELLBEING,
+ ROLE_SYSTEM_SUPERVISION,
+ // Other arbitrarily Strings should not be supported either.
+ "watch",
+ "auto",
+ "companion_device",
+ ""
+ )
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/DisassociateTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/DisassociateTest.kt
new file mode 100644
index 0000000..2184fdc
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/DisassociateTest.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.core
+
+import android.Manifest.permission.MANAGE_COMPANION_DEVICES
+import android.annotation.UserIdInt
+import android.companion.AssociationInfo
+import android.companion.CompanionDeviceManager
+import android.companion.cts.common.MAC_ADDRESS_A
+import android.companion.cts.common.MAC_ADDRESS_B
+import android.companion.cts.common.MAC_ADDRESS_C
+import android.companion.cts.common.assertAssociations
+import android.companion.cts.common.assertEmpty
+import android.net.MacAddress
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertFailsWith
+import kotlin.test.fail
+
+/**
+ * Test CDM APIs for removing existing associations.
+ *
+ * Run: atest CtsCompanionDeviceManagerCoreTestCases:DisassociateTest
+ *
+ * @see android.companion.CompanionDeviceManager.disassociate
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class DisassociateTest : CoreTestBase() {
+ @Test
+ fun test_disassociate_sameApp_singleAssociation() = with(targetApp) {
+ associate(MAC_ADDRESS_A)
+
+ val associations = cdm.myAssociations
+ assertAssociations(
+ actual = associations,
+ expected = setOf(
+ packageName to MAC_ADDRESS_A
+ ))
+
+ cdm.disassociate(associations[0].id)
+ assertEmpty(cdm.myAssociations)
+ }
+
+ @Test
+ fun test_disassociate_sameApp_multipleAssociations() = with(targetApp) {
+ associate(MAC_ADDRESS_A)
+ associate(MAC_ADDRESS_B)
+ associate(MAC_ADDRESS_C)
+ assertAssociations(
+ actual = cdm.myAssociations,
+ expected = setOf(
+ packageName to MAC_ADDRESS_A,
+ packageName to MAC_ADDRESS_B,
+ packageName to MAC_ADDRESS_C
+ ))
+
+ cdm.disassociate(cdm.getMyAssociationLinkedTo(MAC_ADDRESS_A).id)
+ assertAssociations(
+ actual = cdm.myAssociations,
+ expected = setOf(
+ packageName to MAC_ADDRESS_B,
+ packageName to MAC_ADDRESS_C
+ ))
+
+ cdm.disassociate(cdm.getMyAssociationLinkedTo(MAC_ADDRESS_B).id)
+ assertAssociations(
+ actual = cdm.myAssociations,
+ expected = setOf(
+ packageName to MAC_ADDRESS_C
+ ))
+
+ cdm.disassociate(cdm.getMyAssociationLinkedTo(MAC_ADDRESS_C).id)
+ assertEmpty(cdm.myAssociations)
+ }
+
+ @Test
+ fun test_disassociate_anotherApp_requiresPermission() = with(testApp) {
+ associate(MAC_ADDRESS_A)
+ assertAssociations(
+ actual = withShellPermissionIdentity { cdm.allAssociations },
+ expected = setOf(
+ packageName to MAC_ADDRESS_A
+ ))
+
+ val association = withShellPermissionIdentity {
+ cdm.getAssociationForPackage(userId, packageName, MAC_ADDRESS_A)
+ }
+
+ /**
+ * Attempts to remove another app's association without [MANAGE_COMPANION_DEVICES]
+ * permission should throw an Exception and should not change the existing associations.
+ */
+ assertFailsWith(IllegalArgumentException::class) {
+ cdm.disassociate(association.id)
+ }
+ assertAssociations(
+ actual = withShellPermissionIdentity { cdm.allAssociations },
+ expected = setOf(
+ packageName to MAC_ADDRESS_A
+ ))
+
+ /**
+ * Re-running with [MANAGE_COMPANION_DEVICES] permissions: now should succeed and remove
+ * the association.
+ */
+ withShellPermissionIdentity(MANAGE_COMPANION_DEVICES) {
+ cdm.disassociate(association.id)
+ }
+ assertEmpty(
+ withShellPermissionIdentity {
+ cdm.allAssociations
+ })
+ }
+
+ @Test
+ fun test_disassociate_invalidId() {
+ assertEmpty(
+ withShellPermissionIdentity {
+ cdm.allAssociations
+ })
+
+ assertFailsWith(IllegalArgumentException::class) { cdm.disassociate(0) }
+ assertFailsWith(IllegalArgumentException::class) { cdm.disassociate(1) }
+ assertFailsWith(IllegalArgumentException::class) { cdm.disassociate(-1) }
+ }
+
+ private fun CompanionDeviceManager.getMyAssociationLinkedTo(
+ macAddress: MacAddress
+ ): AssociationInfo = myAssociations.find { it.deviceMacAddress == macAddress }
+ ?: fail("Association linked to address $macAddress does not exist")
+
+ private fun CompanionDeviceManager.getAssociationForPackage(
+ @UserIdInt userId: Int,
+ packageName: String,
+ macAddress: MacAddress
+ ): AssociationInfo = allAssociations.find {
+ it.belongsToPackage(userId, packageName) && it.deviceMacAddress == macAddress
+ } ?: fail("Association for u$userId/$packageName linked to address $macAddress does not exist")
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/LegacyDisassociateTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/LegacyDisassociateTest.kt
new file mode 100644
index 0000000..56fe2b3
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/LegacyDisassociateTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.core
+
+import android.Manifest.permission.MANAGE_COMPANION_DEVICES
+import android.companion.cts.common.MAC_ADDRESS_A
+import android.companion.cts.common.MAC_ADDRESS_B
+import android.companion.cts.common.MAC_ADDRESS_C
+import android.companion.cts.common.assertAssociations
+import android.companion.cts.common.assertEmpty
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertFailsWith
+
+/**
+ * Test legacy CDM APIs for removing existing associations (via MAC address)
+ *
+ * Run: atest CtsCompanionDeviceManagerCoreTestCases:DisassociateTest
+ *
+ * @see android.companion.CompanionDeviceManager.disassociate
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class LegacyDisassociateTest : CoreTestBase() {
+ @Test
+ fun test_legacy_disassociate_sameApp() = with(targetApp) {
+ associate(MAC_ADDRESS_A)
+ associate(MAC_ADDRESS_B)
+ associate(MAC_ADDRESS_C)
+ assertAssociations(
+ actual = cdm.myAssociations,
+ expected = setOf(
+ packageName to MAC_ADDRESS_A,
+ packageName to MAC_ADDRESS_B,
+ packageName to MAC_ADDRESS_C
+ ))
+
+ cdm.disassociate(MAC_ADDRESS_A.toString())
+ assertAssociations(
+ actual = cdm.myAssociations,
+ expected = setOf(
+ packageName to MAC_ADDRESS_B,
+ packageName to MAC_ADDRESS_C
+ ))
+
+ cdm.disassociate(MAC_ADDRESS_B.toString())
+ assertAssociations(
+ actual = cdm.myAssociations,
+ expected = setOf(
+ packageName to MAC_ADDRESS_C
+ ))
+
+ cdm.disassociate(MAC_ADDRESS_C.toString())
+ assertEmpty(cdm.myAssociations)
+ }
+
+ @Test
+ fun test_legacy_disassociate_anotherApp() = with(testApp) {
+ associate(MAC_ADDRESS_A)
+ assertAssociations(
+ actual = withShellPermissionIdentity { cdm.allAssociations },
+ expected = setOf(
+ packageName to MAC_ADDRESS_A
+ ))
+
+ /** Cannot remove another app's association by MAC address. */
+ assertFailsWith(IllegalArgumentException::class) {
+ cdm.disassociate(MAC_ADDRESS_A.toString())
+ }
+ assertAssociations(
+ actual = withShellPermissionIdentity { cdm.allAssociations },
+ expected = setOf(
+ packageName to MAC_ADDRESS_A
+ ))
+
+ /** ...not even with [MANAGE_COMPANION_DEVICES] permission. */
+ assertFailsWith(IllegalArgumentException::class) {
+ withShellPermissionIdentity(MANAGE_COMPANION_DEVICES) {
+ cdm.disassociate(MAC_ADDRESS_A.toString())
+ }
+ }
+ assertAssociations(
+ actual = withShellPermissionIdentity { cdm.allAssociations },
+ expected = setOf(
+ packageName to MAC_ADDRESS_A
+ ))
+ }
+
+ @Test
+ fun test_disassociate_invalidId() {
+ assertEmpty(
+ withShellPermissionIdentity {
+ cdm.allAssociations
+ })
+
+ assertFailsWith(IllegalArgumentException::class) {
+ cdm.disassociate(MAC_ADDRESS_A.toString())
+ }
+ assertFailsWith(IllegalArgumentException::class) {
+ cdm.disassociate(MAC_ADDRESS_B.toString())
+ }
+ assertFailsWith(IllegalArgumentException::class) {
+ cdm.disassociate(MAC_ADDRESS_C.toString())
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/RetrieveAssociationsTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/RetrieveAssociationsTest.kt
new file mode 100644
index 0000000..b37000f
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/RetrieveAssociationsTest.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.core
+
+import android.Manifest
+import android.Manifest.permission.MANAGE_COMPANION_DEVICES
+import android.companion.cts.common.MAC_ADDRESS_A
+import android.companion.cts.common.MAC_ADDRESS_B
+import android.companion.cts.common.MAC_ADDRESS_C
+import android.companion.cts.common.assertAssociations
+import android.companion.cts.common.assertEmpty
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+
+/**
+ * Test CDM APIs for retrieving the list of existing associations.
+ *
+ * Run: atest CtsCompanionDeviceManagerCoreTestCases:RetrieveAssociationsTest
+ *
+ * @see android.companion.CompanionDeviceManager.getAllAssociations
+ * @see android.companion.CompanionDeviceManager.getMyAssociations
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class RetrieveAssociationsTest : CoreTestBase() {
+
+ @Test
+ fun test_getMyAssociations_singleAssociation() = with(targetApp) {
+ assertEmpty(cdm.myAssociations)
+
+ associate(MAC_ADDRESS_A)
+ assertAssociations(
+ actual = cdm.myAssociations,
+ expected = setOf(
+ packageName to MAC_ADDRESS_A
+ ))
+
+ disassociate(MAC_ADDRESS_A)
+ assertEmpty(cdm.myAssociations)
+ }
+
+ @Test
+ fun test_getMyAssociations_multipleAssociations() = with(targetApp) {
+ assertEmpty(cdm.myAssociations)
+
+ associate(MAC_ADDRESS_A)
+ assertAssociations(
+ actual = cdm.myAssociations,
+ expected = setOf(
+ packageName to MAC_ADDRESS_A
+ ))
+
+ associate(MAC_ADDRESS_B)
+ assertAssociations(
+ actual = cdm.myAssociations,
+ expected = setOf(
+ packageName to MAC_ADDRESS_A,
+ packageName to MAC_ADDRESS_B
+ ))
+
+ associate(MAC_ADDRESS_C)
+ assertAssociations(
+ actual = cdm.myAssociations,
+ expected = setOf(
+ packageName to MAC_ADDRESS_A,
+ packageName to MAC_ADDRESS_B,
+ packageName to MAC_ADDRESS_C
+ ))
+
+ disassociate(MAC_ADDRESS_A)
+ assertAssociations(
+ actual = cdm.myAssociations,
+ expected = setOf(
+ packageName to MAC_ADDRESS_B,
+ packageName to MAC_ADDRESS_C
+ ))
+
+ disassociate(MAC_ADDRESS_B)
+ assertAssociations(
+ actual = cdm.myAssociations,
+ expected = setOf(
+ packageName to MAC_ADDRESS_C
+ ))
+
+ disassociate(MAC_ADDRESS_C)
+ assertEmpty(cdm.myAssociations)
+ }
+
+ @Test
+ fun test_getMyAssociations_otherPackages_NotIncluded() {
+ testApp.associate(MAC_ADDRESS_A)
+ assertEmpty(cdm.myAssociations)
+
+ targetApp.associate(MAC_ADDRESS_B)
+ assertAssociations(
+ actual = cdm.myAssociations,
+ expected = setOf(targetApp.packageName to MAC_ADDRESS_B))
+
+ testApp.associate(MAC_ADDRESS_C)
+ assertAssociations(
+ actual = cdm.myAssociations,
+ expected = setOf(targetApp.packageName to MAC_ADDRESS_B))
+
+ targetApp.disassociate(MAC_ADDRESS_B)
+ assertEmpty(cdm.myAssociations)
+ }
+
+ @Test
+ fun test_getAllAssociations_requiresPermission() {
+ /**
+ * Attempts to get the list of all associations for the (current) user without
+ * [MANAGE_COMPANION_DEVICES] permission should throw a [SecurityException].
+ */
+ assertFailsWith(SecurityException::class) {
+ cdm.allAssociations
+ }
+
+ /**
+ * Re-running with [MANAGE_COMPANION_DEVICES] permissions: now should succeed and return a
+ * non-null list.
+ */
+ assertNotNull(
+ withShellPermissionIdentity(MANAGE_COMPANION_DEVICES) {
+ cdm.allAssociations
+ })
+ }
+
+ @Test
+ fun test_getAllAssociations_sameApp() = with(targetApp) {
+ associate(MAC_ADDRESS_A)
+ assertAssociations(
+ actual = withShellPermissionIdentity { cdm.allAssociations },
+ expected = setOf(
+ packageName to MAC_ADDRESS_A
+ ))
+
+ disassociate(MAC_ADDRESS_A)
+ assertEmpty(withShellPermissionIdentity { cdm.allAssociations })
+ }
+
+ @Test
+ fun test_getAllAssociations_otherApps() = with(testApp) {
+ associate(MAC_ADDRESS_A)
+ assertAssociations(
+ actual = withShellPermissionIdentity { cdm.allAssociations },
+ expected = setOf(
+ packageName to MAC_ADDRESS_A
+ ))
+
+ disassociate(MAC_ADDRESS_A)
+ assertEmpty(withShellPermissionIdentity { cdm.allAssociations })
+ }
+
+ @Test
+ fun test_getAllAssociations_sameAndOtherApps() {
+ targetApp.associate(MAC_ADDRESS_A)
+ testApp.associate(MAC_ADDRESS_B)
+
+ assertAssociations(
+ actual = withShellPermissionIdentity { cdm.allAssociations },
+ expected = setOf(
+ targetApp.packageName to MAC_ADDRESS_A,
+ testApp.packageName to MAC_ADDRESS_B
+ ))
+ }
+
+ @Test
+ fun test_ObservePresence_updatesAssociationInfo() {
+ targetApp.associate(MAC_ADDRESS_A)
+
+ // Ensure the new association is stored in the CDM
+ val associationsBeforeChange = cdm.myAssociations
+ assertEquals(1, associationsBeforeChange.size)
+ val associationInfo = cdm.myAssociations.first()
+
+ // update the underlying association
+ withShellPermissionIdentity(Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) {
+ cdm.startObservingDevicePresence(MAC_ADDRESS_A.toString())
+ }
+
+ // Assert the association change was reflected
+ val associationsAfterUpdate = cdm.myAssociations
+ assertEquals(1, associationsAfterUpdate.size)
+ assertNotEquals(
+ actual = associationsAfterUpdate.first(),
+ illegal = associationInfo
+ )
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/SelfPresenceReportingTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/SelfPresenceReportingTest.kt
new file mode 100644
index 0000000..694f8d9
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/SelfPresenceReportingTest.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.core
+
+import android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED
+import android.companion.AssociationRequest
+import android.companion.cts.common.DEVICE_DISPLAY_NAME_A
+import android.companion.cts.common.DEVICE_DISPLAY_NAME_B
+import android.companion.cts.common.MAC_ADDRESS_A
+import android.companion.cts.common.PrimaryCompanionService
+import android.companion.cts.common.RecordingCallback
+import android.companion.cts.common.RecordingCallback.OnAssociationCreated
+import android.companion.cts.common.SIMPLE_EXECUTOR
+import android.companion.cts.common.SecondaryCompanionService
+import android.companion.cts.common.assertEmpty
+import android.companion.cts.common.waitFor
+import android.os.SystemClock.sleep
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertContentEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertIs
+import kotlin.test.assertTrue
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+import kotlin.test.assertFailsWith
+
+/**
+ * Tests CDM APIs for notifying the presence of status of the companion devices for self-managed
+ * associations.
+ *
+ * Build/Install/Run: atest CtsCompanionDeviceManagerCoreTestCases:SelfPresenceReportingTest
+ *
+ * @see android.companion.CompanionDeviceManager.notifyDeviceAppeared
+ * @see android.companion.CompanionDeviceManager.notifyDeviceDisappeared
+ * @see android.companion.CompanionDeviceService.onDeviceAppeared
+ * @see android.companion.CompanionDeviceService.onDeviceDisappeared
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class SelfPresenceReportingTest : CoreTestBase() {
+
+ @Test
+ fun test_primaryService_isBound() =
+ withShellPermissionIdentity(REQUEST_COMPANION_SELF_MANAGED) {
+ val associationId = createSelfManagedAssociation(DEVICE_DISPLAY_NAME_A)
+
+ cdm.notifyDeviceAppeared(associationId)
+
+ assertTrue("Both Services - Primary and Secondary - should be bound now") {
+ waitFor(timeout = 1.seconds, interval = 100.milliseconds) {
+ PrimaryCompanionService.isBound && SecondaryCompanionService.isBound
+ }
+ }
+
+ // Check that only the primary services has received the onDeviceAppeared() callback...
+ PrimaryCompanionService.waitAssociationToAppear(associationId)
+ assertContentEquals(
+ actual = PrimaryCompanionService.associationIdsForConnectedDevices,
+ expected = setOf(associationId)
+ )
+ // ... while the non-primary service - has NOT. (Give it 1 more second.)
+ sleep(1000)
+ assertEmpty(SecondaryCompanionService.connectedDevices)
+
+ assertFalse("Both Services - Primary and Secondary - should stay bound") {
+ waitFor(timeout = 3.seconds, interval = 1.seconds) {
+ !PrimaryCompanionService.isBound || !SecondaryCompanionService.isBound
+ }
+ }
+
+ cdm.notifyDeviceDisappeared(associationId)
+
+ // Check that only the primary services has received the onDeviceDisappeared() callback.
+ PrimaryCompanionService.waitAssociationToDisappear(associationId)
+ assertEmpty(PrimaryCompanionService.connectedDevices)
+
+ assertTrue("Both Services - Primary and Secondary - should be unbound now") {
+ waitFor(timeout = 1.seconds, interval = 100.milliseconds) {
+ !PrimaryCompanionService.isBound && !SecondaryCompanionService.isBound
+ }
+ }
+ }
+
+ @Test
+ @Ignore("b/211398735")
+ fun test_multipleDevices_sameApplication() {
+ val idA = createSelfManagedAssociation(DEVICE_DISPLAY_NAME_A)
+ val idB = createSelfManagedAssociation(DEVICE_DISPLAY_NAME_B)
+
+ cdm.notifyDeviceAppeared(idA)
+
+ assertTrue("Both Services - Primary and Secondary - should be bound now") {
+ waitFor(timeout = 1.seconds, interval = 100.milliseconds) {
+ PrimaryCompanionService.isBound && SecondaryCompanionService.isBound
+ }
+ }
+
+ // Check that only the primary services has received the onDeviceAppeared() callback...
+ PrimaryCompanionService.waitAssociationToAppear(idA)
+ assertContentEquals(
+ actual = PrimaryCompanionService.associationIdsForConnectedDevices,
+ expected = setOf(idA)
+ )
+ // ... while the non-primary service - has NOT. (Give it 1 more second.)
+ sleep(1000)
+ assertEmpty(SecondaryCompanionService.connectedDevices)
+
+ cdm.notifyDeviceAppeared(idB)
+
+ // Check that only the primary services has received the onDeviceAppeared() callback...
+ PrimaryCompanionService.waitAssociationToAppear(idB)
+ assertContentEquals(
+ actual = PrimaryCompanionService.associationIdsForConnectedDevices,
+ expected = setOf(idA, idB)
+ )
+
+ // Make sure both services stay bound.
+ assertFalse("Both Services - Primary and Secondary - should stay bound") {
+ waitFor(timeout = 3.seconds, interval = 1.seconds) {
+ !PrimaryCompanionService.isBound || !SecondaryCompanionService.isBound
+ }
+ }
+
+ // "Disconnect" first device (A).
+ cdm.notifyDeviceDisappeared(idA)
+
+ PrimaryCompanionService.waitAssociationToDisappear(idA)
+ // Both services should stay bound for as long as there is at least
+ // one connected device (B).
+ assertFalse("Both Services - Primary and Secondary - should stay bound") {
+ waitFor(timeout = 3.seconds, interval = 1.seconds) {
+ !PrimaryCompanionService.isBound || !SecondaryCompanionService.isBound
+ }
+ }
+
+ // "Disconnect" second device (B).
+ cdm.notifyDeviceDisappeared(idB)
+
+ PrimaryCompanionService.waitAssociationToDisappear(idB)
+ assertTrue("Both Services - Primary and Secondary - should be unbound now") {
+ waitFor(timeout = 1.seconds, interval = 100.milliseconds) {
+ !PrimaryCompanionService.isBound && !SecondaryCompanionService.isBound
+ }
+ }
+ }
+
+ @Test
+ fun test_notifyAppearAndDisappear_invalidId() {
+ assertFailsWith(IllegalArgumentException::class) { cdm.notifyDeviceAppeared(-1) }
+ assertFailsWith(IllegalArgumentException::class) { cdm.notifyDeviceAppeared(0) }
+ assertFailsWith(IllegalArgumentException::class) { cdm.notifyDeviceAppeared(1) }
+
+ assertFailsWith(IllegalArgumentException::class) { cdm.notifyDeviceDisappeared(-1) }
+ assertFailsWith(IllegalArgumentException::class) { cdm.notifyDeviceDisappeared(0) }
+ assertFailsWith(IllegalArgumentException::class) { cdm.notifyDeviceDisappeared(1) }
+ }
+
+ @Test
+ fun test_notifyAppears_requires_selfManagedAssociation() {
+ targetApp.associate(MAC_ADDRESS_A)
+
+ val id = cdm.myAssociations[0].id
+
+ // notifyDeviceAppeared can only be called for self-managed associations.
+ assertFailsWith(IllegalArgumentException::class) {
+ cdm.notifyDeviceAppeared(id)
+ }
+ }
+
+ private fun createSelfManagedAssociation(displayName: String): Int {
+ val callback = RecordingCallback()
+ val request: AssociationRequest = AssociationRequest.Builder()
+ .setSelfManaged(true)
+ .setDisplayName(displayName)
+ .build()
+ callback.assertInvokedByActions {
+ withShellPermissionIdentity(REQUEST_COMPANION_SELF_MANAGED) {
+ cdm.associate(request, SIMPLE_EXECUTOR, callback)
+ }
+ }
+
+ val callbackInvocation = callback.invocations.first()
+ assertIs<OnAssociationCreated>(callbackInvocation)
+ return callbackInvocation.associationInfo.id
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/uiautomation/AndroidManifest.xml b/tests/tests/companion/uiautomation/AndroidManifest.xml
new file mode 100644
index 0000000..a6052fe
--- /dev/null
+++ b/tests/tests/companion/uiautomation/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.companion.cts.uiautomation">
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.companion.cts.uiautomation"
+ android:label="CompanionDeviceManager UiAutomation CTS tests">
+
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/companion/uiautomation/AndroidTest.xml b/tests/tests/companion/uiautomation/AndroidTest.xml
new file mode 100644
index 0000000..9982689
--- /dev/null
+++ b/tests/tests/companion/uiautomation/AndroidTest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="Configuration for UiAutomation CTS tests for CompanionDeviceManager">
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <option name="not-shardable" value="true" />
+ <option name="test-suite-tag" value="cts" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsCompanionDeviceManagerUiAutomationTestCases.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.companion.cts.uiautomation" />
+ </test>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
+ <option name="run-command" value="am wait-for-broadcast-idle" />
+ </target_preparer>
+</configuration>
diff --git a/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndSelfManagedTest.kt b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndSelfManagedTest.kt
new file mode 100644
index 0000000..4528b4e
--- /dev/null
+++ b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndSelfManagedTest.kt
@@ -0,0 +1,98 @@
+package android.companion.cts.uiautomation
+
+import android.app.Activity
+import android.companion.AssociationInfo
+import android.companion.AssociationRequest.DEVICE_PROFILE_WATCH
+import android.companion.CompanionDeviceManager
+import android.companion.cts.common.CompanionActivity
+import android.companion.cts.common.RecordingCallback.OnAssociationCreated
+import android.content.Intent
+import android.platform.test.annotations.AppModeFull
+import org.junit.Assume.assumeFalse
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+import kotlin.test.assertNotNull
+
+/**
+ * Tests the Association Flow end-to-end.
+ *
+ * Build/Install/Run:
+ * atest CtsCompanionDeviceManagerUiAutomationTestCases:AssociationEndToEndSelfManagedTest
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(Parameterized::class)
+class AssociationEndToEndSelfManagedTest(
+ profile: String?,
+ profilePermission: String?,
+ profileName: String // Used only by the Parameterized test runner for tagging.
+) : UiAutomationTestBase(profile, profilePermission) {
+
+ override fun setUp() {
+ super.setUp()
+
+ // TODO(b/211602270): Add support for WATCH and "null" profiles in the
+ // confirmation UI (the "self-managed" flow variant).
+ assumeFalse(profile == null)
+ assumeFalse(profile == DEVICE_PROFILE_WATCH)
+ }
+
+ @Test
+ fun test_userRejected() = super.test_userRejected(
+ singleDevice = false, selfManaged = true, displayName = DEVICE_DISPLAY_NAME)
+
+ @Test
+ fun test_userDismissed() = super.test_userDismissed(
+ singleDevice = false, selfManaged = true, displayName = DEVICE_DISPLAY_NAME)
+
+ @Test
+ fun test_userConfirmed() {
+ sendRequestAndLaunchConfirmation(selfManaged = true, displayName = DEVICE_DISPLAY_NAME)
+
+ callback.assertInvokedByActions {
+ // User "approves" the request.
+ confirmationUi.clickPositiveButton()
+ }
+ // Check callback invocations: there should have been exactly 1 invocation of the
+ // OnAssociationCreated() method.
+ assertEquals(1, callback.invocations.size)
+ val associationInvocation = callback.invocations.first()
+ assertIs<OnAssociationCreated>(associationInvocation)
+ val associationFromCallback = associationInvocation.associationInfo
+ assertEquals(actual = associationFromCallback.displayName, expected = DEVICE_DISPLAY_NAME)
+
+ // Wait until the Confirmation UI goes away.
+ confirmationUi.waitUntilGone()
+
+ // Check the result code and the data delivered via onActivityResult()
+ val (resultCode: Int, data: Intent?) = CompanionActivity.waitForActivityResult()
+ assertEquals(actual = resultCode, expected = Activity.RESULT_OK)
+ assertNotNull(data)
+ val associationFromActivityResult: AssociationInfo? =
+ data.getParcelableExtra(CompanionDeviceManager.EXTRA_ASSOCIATION)
+ assertNotNull(associationFromActivityResult)
+ // Check that the association reported back via the callback same as the association
+ // delivered via onActivityResult().
+ assertEquals(associationFromCallback, associationFromActivityResult)
+
+ // Make sure getMyAssociations() returns the same association we received via the callback
+ // as well as in onActivityResult()
+ assertContentEquals(actual = cdm.myAssociations, expected = listOf(associationFromCallback))
+ }
+
+ companion object {
+ /**
+ * List of (profile, permission, name) tuples that represent all supported profiles and
+ * null.
+ * Each test will be suffixed with "[profile=<NAME>]", e.g.: "[profile=WATCH]".
+ */
+ @Parameterized.Parameters(name = "profile={2}")
+ @JvmStatic
+ fun parameters() = supportedProfilesAndNull()
+
+ private const val DEVICE_DISPLAY_NAME = "My Device"
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndSingleDeviceTest.kt b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndSingleDeviceTest.kt
new file mode 100644
index 0000000..a853ae3
--- /dev/null
+++ b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndSingleDeviceTest.kt
@@ -0,0 +1,55 @@
+package android.companion.cts.uiautomation
+
+import android.platform.test.annotations.AppModeFull
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Tests the Association Flow end-to-end.
+ *
+ * Build/Install/Run:
+ * atest CtsCompanionDeviceManagerUiAutomationTestCases:AssociationEndToEndSingleDeviceTest
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(Parameterized::class)
+class AssociationEndToEndSingleDeviceTest(
+ profile: String?,
+ profilePermission: String?,
+ profileName: String // Used only by the Parameterized test runner for tagging.
+) : UiAutomationTestBase(profile, profilePermission) {
+
+ @Test
+ @Ignore("b/211722613")
+ fun test_userRejected() =
+ super.test_userRejected(singleDevice = true, selfManaged = false, displayName = null)
+
+ @Test
+ @Ignore("b/211722613")
+ fun test_userDismissed() =
+ super.test_userDismissed(singleDevice = true, selfManaged = false, displayName = null)
+
+ @Test
+ @Ignore("b/211722613")
+ fun test_timeout() = super.test_timeout(singleDevice = true)
+
+ @Test
+ @Ignore("b/211722613")
+ fun test_userConfirmed() = super.test_userConfirmed_foundDevice(singleDevice = true) {
+ // Wait until a device is found, which should activate the "positive" button, and click on
+ // the button.
+ confirmationUi.waitUntilPositiveButtonIsEnabledAndClick()
+ }
+
+ companion object {
+ /**
+ * List of (profile, permission, name) tuples that represent all supported profiles and
+ * null.
+ * Each test will be suffixed with "[profile=<NAME>]", e.g.: "[profile=WATCH]".
+ */
+ @Parameterized.Parameters(name = "profile={2}")
+ @JvmStatic
+ fun parameters() = supportedProfilesAndNull()
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndTest.kt b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndTest.kt
new file mode 100644
index 0000000..2aafd51
--- /dev/null
+++ b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.uiautomation
+
+import android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING
+import android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION
+import android.platform.test.annotations.AppModeFull
+import org.junit.Assume.assumeFalse
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Tests the Association Flow end-to-end.
+ *
+ * Build/Install/Run: atest CtsCompanionDeviceManagerUiAutomationTestCases:AssociationEndToEndTest
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(Parameterized::class)
+class AssociationEndToEndTest(
+ profile: String?,
+ profilePermission: String?,
+ profileName: String // Used only by the Parameterized test runner for tagging.
+) : UiAutomationTestBase(profile, profilePermission) {
+
+ override fun setUp() {
+ super.setUp()
+
+ // TODO(b/211590680): Add support for APP_STREAMING and AUTOMOTIVE_PROJECTION in the
+ // confirmation UI (the "multiple devices" flow variant).
+ assumeFalse(profile == DEVICE_PROFILE_APP_STREAMING)
+ assumeFalse(profile == DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)
+ }
+
+ @Test
+ fun test_userRejected() =
+ super.test_userRejected(singleDevice = false, selfManaged = false, displayName = null)
+
+ @Test
+ fun test_userDismissed() =
+ super.test_userDismissed(singleDevice = false, selfManaged = false, displayName = null)
+
+ @Test
+ fun test_userConfirmed() = super.test_userConfirmed_foundDevice(singleDevice = false) {
+ // Wait until at least one device is found and click on it.
+ confirmationUi.waitAndClickOnFirstFoundDevice()
+ }
+
+ @Test
+ fun test_timeout() = super.test_timeout(singleDevice = false)
+
+ companion object {
+ /**
+ * List of (profile, permission, name) tuples that represent all supported profiles and
+ * null.
+ * Each test will be suffixed with "[profile=<NAME>]", e.g.: "[profile=WATCH]".
+ */
+ @Parameterized.Parameters(name = "profile={2}")
+ @JvmStatic
+ fun parameters() = supportedProfilesAndNull()
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/CompanionDeviceManagerUi.kt b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/CompanionDeviceManagerUi.kt
new file mode 100644
index 0000000..b92e8fe
--- /dev/null
+++ b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/CompanionDeviceManagerUi.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.uiautomation
+
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.SearchCondition
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+
+class CompanionDeviceManagerUi(private val ui: UiDevice) {
+ val isVisible: Boolean
+ get() = ui.hasObject(CONFIRMATION_UI)
+
+ fun dismiss() {
+ if (!isVisible) return
+ // Pressing back button should close (cancel) confirmation UI.
+ ui.pressBack()
+ waitUntilGone()
+ }
+
+ fun waitUntilVisible() = ui.wait(Until.hasObject(CONFIRMATION_UI), "CDM UI has not appeared.")
+
+ fun waitUntilGone() = ui.waitShort(Until.gone(CONFIRMATION_UI), "CDM UI has not disappeared")
+
+ fun waitAndClickOnFirstFoundDevice() = ui.waitLongAndFind(
+ Until.findObject(DEVICE_LIST_WITH_ITEMS), "Device List not found or empty")
+ .children[0].click()
+
+ fun waitUntilPositiveButtonIsEnabledAndClick() = ui.waitLongAndFind(
+ Until.findObject(POSITIVE_BUTTON), "Positive button not found or not clickable")
+ .click()
+
+ fun clickPositiveButton() = click(POSITIVE_BUTTON, "Positive button")
+
+ fun clickNegativeButton() = click(NEGATIVE_BUTTON, "Negative button")
+
+ private fun click(selector: BySelector, description: String) = ui.waitShortAndFind(
+ Until.findObject(selector), "$description is not found")
+ .click()
+
+ companion object {
+ private const val PACKAGE_NAME = "com.android.companiondevicemanager"
+
+ private val CONFIRMATION_UI = By.pkg(PACKAGE_NAME)
+ .res(PACKAGE_NAME, "activity_confirmation")
+
+ private val CLICKABLE_BUTTON =
+ By.pkg(PACKAGE_NAME).clazz(".Button").clickable(true)
+ private val POSITIVE_BUTTON = By.copy(CLICKABLE_BUTTON).res(PACKAGE_NAME, "btn_positive")
+ private val NEGATIVE_BUTTON = By.copy(CLICKABLE_BUTTON).res(PACKAGE_NAME, "btn_negative")
+
+ private val DEVICE_LIST = By.pkg(PACKAGE_NAME).clazz(".ListView")
+ .res(PACKAGE_NAME, "device_list")
+ private val DEVICE_LIST_ITEM = By.pkg(PACKAGE_NAME)
+ .res(PACKAGE_NAME, "list_item_device")
+ private val DEVICE_LIST_WITH_ITEMS = By.copy(DEVICE_LIST)
+ .hasChild(DEVICE_LIST_ITEM)
+ }
+
+ private fun UiDevice.wait(
+ condition: SearchCondition<Boolean>,
+ message: String,
+ timeout: Duration = 3.seconds
+ ) {
+ if (!wait(condition, timeout.inWholeMilliseconds)) error(message)
+ }
+
+ private fun UiDevice.waitShort(condition: SearchCondition<Boolean>, message: String) =
+ wait(condition, message, 1.seconds)
+
+ private fun UiDevice.waitAndFind(
+ condition: SearchCondition<UiObject2>,
+ message: String,
+ timeout: Duration = 3.seconds
+ ): UiObject2 =
+ wait(condition, timeout.inWholeMilliseconds) ?: error(message)
+
+ private fun UiDevice.waitShortAndFind(
+ condition: SearchCondition<UiObject2>,
+ message: String
+ ): UiObject2 = waitAndFind(condition, message, 1.seconds)
+
+ private fun UiDevice.waitLongAndFind(
+ condition: SearchCondition<UiObject2>,
+ message: String
+ ): UiObject2 = waitAndFind(condition, message, 10.seconds)
+}
\ No newline at end of file
diff --git a/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/UiAutomationTestBase.kt b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/UiAutomationTestBase.kt
new file mode 100644
index 0000000..4d1f3b3
--- /dev/null
+++ b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/UiAutomationTestBase.kt
@@ -0,0 +1,289 @@
+package android.companion.cts.uiautomation
+
+import android.Manifest
+import android.annotation.CallSuper
+import android.app.Activity
+import android.companion.AssociationInfo
+import android.companion.AssociationRequest
+import android.companion.BluetoothDeviceFilter
+import android.companion.BluetoothDeviceFilterUtils
+import android.companion.CompanionDeviceManager
+import android.companion.DeviceFilter
+import android.companion.cts.common.CompanionActivity
+import android.companion.cts.common.DEVICE_PROFILES
+import android.companion.cts.common.DEVICE_PROFILE_TO_NAME
+import android.companion.cts.common.DEVICE_PROFILE_TO_PERMISSION
+import android.companion.cts.common.RecordingCallback
+import android.companion.cts.common.RecordingCallback.OnAssociationCreated
+import android.companion.cts.common.RecordingCallback.OnAssociationPending
+import android.companion.cts.common.RecordingCallback.OnFailure
+import android.companion.cts.common.SIMPLE_EXECUTOR
+import android.companion.cts.common.TestBase
+import android.companion.cts.common.assertEmpty
+import android.companion.cts.common.setSystemProp
+import android.content.Intent
+import android.net.MacAddress
+import android.os.Parcelable
+import androidx.test.uiautomator.UiDevice
+import org.junit.Assume
+import org.junit.Assume.assumeFalse
+import java.util.regex.Pattern
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+import kotlin.test.assertNotNull
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.ZERO
+import kotlin.time.Duration.Companion.seconds
+
+open class UiAutomationTestBase(
+ protected val profile: String?,
+ private val profilePermission: String?
+) : TestBase() {
+ private val uiDevice: UiDevice by lazy { UiDevice.getInstance(instrumentation) }
+ protected val confirmationUi by lazy { CompanionDeviceManagerUi(uiDevice) }
+ protected val callback by lazy { RecordingCallback() }
+
+ @CallSuper
+ override fun tearDown() {
+ super.tearDown()
+
+ CompanionActivity.safeFinish()
+ confirmationUi.dismiss()
+
+ restoreDiscoveryTimeout()
+ }
+
+ @CallSuper
+ override fun setUp() {
+ super.setUp()
+
+ assumeFalse(confirmationUi.isVisible)
+ Assume.assumeTrue(CompanionActivity.waitUntilGone())
+ uiDevice.waitForIdle()
+
+ callback.clearRecordedInvocations()
+ }
+
+ protected fun test_userRejected(
+ singleDevice: Boolean = false,
+ selfManaged: Boolean = false,
+ displayName: String? = null
+ ) = test_cancelled(singleDevice, selfManaged, displayName) {
+ // User "rejects" the request.
+ confirmationUi.clickNegativeButton()
+ }
+
+ protected fun test_userDismissed(
+ singleDevice: Boolean = false,
+ selfManaged: Boolean = false,
+ displayName: String? = null
+ ) = test_cancelled(singleDevice, selfManaged, displayName) {
+ // User "dismisses" the request.
+ uiDevice.pressBack()
+ }
+
+ private fun test_cancelled(
+ singleDevice: Boolean,
+ selfManaged: Boolean,
+ displayName: String?,
+ cancelAction: () -> Unit
+ ) {
+ sendRequestAndLaunchConfirmation(singleDevice, selfManaged, displayName)
+
+ callback.assertInvokedByActions {
+ cancelAction()
+ }
+ // Check callback invocations: there should have been exactly 1 invocation of the
+ // onFailure() method.
+ assertContentEquals(
+ actual = callback.invocations,
+ expected = listOf(OnFailure("Cancelled."))
+ )
+
+ // Wait until the Confirmation UI goes away.
+ confirmationUi.waitUntilGone()
+
+ // Check the result code delivered via onActivityResult()
+ val (resultCode: Int, _) = CompanionActivity.waitForActivityResult()
+ assertEquals(actual = resultCode, expected = Activity.RESULT_CANCELED)
+
+ // Make sure no Associations were created.
+ assertEmpty(cdm.myAssociations)
+ }
+
+ protected fun test_timeout(singleDevice: Boolean = false) {
+ setDiscoveryTimeout(1.seconds)
+
+ // The discovery timeout is 1 sec, but let's give it 2.
+ callback.assertInvokedByActions(2.seconds) {
+ // Make sure no device will match the request
+ sendRequestAndLaunchConfirmation(
+ singleDevice = singleDevice,
+ deviceFilter = UNMATCHABLE_BT_FILTER
+ )
+ }
+
+ // Check callback invocations: there should have been exactly 1 invocation of the
+ // onFailure() method.
+ assertContentEquals(
+ actual = callback.invocations,
+ expected = listOf(OnFailure("Timeout."))
+ )
+
+ // Wait until the Confirmation UI goes away.
+ confirmationUi.waitUntilGone()
+
+ // Check the result code delivered via onActivityResult()
+ val (resultCode: Int, _) = CompanionActivity.waitForActivityResult()
+ assertEquals(actual = resultCode, expected = Activity.RESULT_CANCELED)
+
+ // Make sure no Associations were created.
+ assertEmpty(cdm.myAssociations)
+ }
+
+ protected fun test_userConfirmed_foundDevice(
+ singleDevice: Boolean,
+ confirmationAction: () -> Unit
+ ) {
+ sendRequestAndLaunchConfirmation(singleDevice = singleDevice)
+
+ callback.assertInvokedByActions {
+ confirmationAction()
+ }
+ // Check callback invocations: there should have been exactly 1 invocation of the
+ // OnAssociationCreated() method.
+ assertEquals(1, callback.invocations.size)
+ val associationInvocation = callback.invocations.first()
+ assertIs<OnAssociationCreated>(associationInvocation)
+ val associationFromCallback = associationInvocation.associationInfo
+
+ // Wait until the Confirmation UI goes away.
+ confirmationUi.waitUntilGone()
+
+ // Check the result code and the data delivered via onActivityResult()
+ val (resultCode: Int, data: Intent?) = CompanionActivity.waitForActivityResult()
+ assertEquals(actual = resultCode, expected = Activity.RESULT_OK)
+ assertNotNull(data)
+ val associationFromActivityResult: AssociationInfo? =
+ data.getParcelableExtra(CompanionDeviceManager.EXTRA_ASSOCIATION)
+ assertNotNull(associationFromActivityResult)
+ // Check that the association reported back via the callback same as the association
+ // delivered via onActivityResult().
+ assertEquals(associationFromCallback, associationFromActivityResult)
+
+ // Make sure "device data" was included (for backwards compatibility), and that the
+ // MAC address extracted from this data matches the MAC address from AssociationInfo.
+ val deviceFromActivityResult: Parcelable? =
+ data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)
+ assertNotNull(deviceFromActivityResult)
+
+ val deviceMacAddress =
+ BluetoothDeviceFilterUtils.getDeviceMacAddress(deviceFromActivityResult)
+ assertEquals(actual = MacAddress.fromString(deviceMacAddress),
+ expected = associationFromCallback.deviceMacAddress)
+
+ // Make sure getMyAssociations() returns the same association we received via the callback
+ // as well as in onActivityResult()
+ assertContentEquals(actual = cdm.myAssociations, expected = listOf(associationFromCallback))
+ }
+
+ protected fun sendRequestAndLaunchConfirmation(
+ singleDevice: Boolean = false,
+ selfManaged: Boolean = false,
+ displayName: String? = null,
+ deviceFilter: DeviceFilter<*>? = null
+ ) {
+ val request = AssociationRequest.Builder()
+ .apply {
+ // Set the single-device flag.
+ setSingleDevice(singleDevice)
+
+ // Set the self-managed flag.
+ setSelfManaged(selfManaged)
+
+ // Set profile if not null.
+ profile?.let { setDeviceProfile(it) }
+
+ // Set display name if not null.
+ displayName?.let { setDisplayName(it) }
+
+ // Add device filter if not null.
+ deviceFilter?.let { addDeviceFilter(it) }
+ }
+ .build()
+ callback.clearRecordedInvocations()
+
+ callback.assertInvokedByActions {
+ // If the REQUEST_COMPANION_SELF_MANAGED and/or the profile permission is required:
+ // run with these permissions as the Shell;
+ // otherwise: just call associate().
+ with(getRequiredPermissions(selfManaged)) {
+ if (isNotEmpty()) {
+ withShellPermissionIdentity(*toTypedArray()) {
+ cdm.associate(request, SIMPLE_EXECUTOR, callback)
+ }
+ } else {
+ cdm.associate(request, SIMPLE_EXECUTOR, callback)
+ }
+ }
+ }
+ // Check callback invocations: there should have been exactly 1 invocation of the
+ // onAssociationPending() method.
+
+ assertEquals(1, callback.invocations.size)
+ val associationInvocation = callback.invocations.first()
+ assertIs<OnAssociationPending>(associationInvocation)
+
+ // Get intent sender and clear callback invocations.
+ val pendingConfirmation = associationInvocation.intentSender
+ callback.clearRecordedInvocations()
+
+ // Launch CompanionActivity, and then launch confirmation UI from it.
+ CompanionActivity.launchAndWait(context)
+ CompanionActivity.startIntentSender(pendingConfirmation)
+
+ confirmationUi.waitUntilVisible()
+ }
+
+ private fun getRequiredPermissions(selfManaged: Boolean): List<String> =
+ mutableListOf<String>().also {
+ if (selfManaged) it += Manifest.permission.REQUEST_COMPANION_SELF_MANAGED
+ if (profilePermission != null) it += profilePermission
+ }
+
+ private fun setDiscoveryTimeout(timeout: Duration) =
+ instrumentation.setSystemProp(
+ SYS_PROP_DEBUG_TIMEOUT,
+ timeout.inWholeMilliseconds.toString()
+ )
+
+ private fun restoreDiscoveryTimeout() = setDiscoveryTimeout(ZERO)
+
+ companion object {
+ /**
+ * List of (profile, permission, name) tuples that represent all supported profiles and
+ * null.
+ */
+ @JvmStatic
+ protected fun supportedProfilesAndNull() = mutableListOf<Array<String?>>().apply {
+ add(arrayOf(null, null, "null"))
+ addAll(supportedProfiles())
+ }
+
+ /** List of (profile, permission, name) tuples that represent all supported profiles. */
+ private fun supportedProfiles(): Collection<Array<String?>> = DEVICE_PROFILES.map {
+ profile ->
+ arrayOf(profile,
+ DEVICE_PROFILE_TO_PERMISSION[profile]!!,
+ DEVICE_PROFILE_TO_NAME[profile]!!)
+ }
+
+ private val UNMATCHABLE_BT_FILTER = BluetoothDeviceFilter.Builder()
+ .setAddress("FF:FF:FF:FF:FF:FF")
+ .setNamePattern(Pattern.compile("This Device Does Not Exist"))
+ .build()
+
+ private const val SYS_PROP_DEBUG_TIMEOUT = "debug.cdm.discovery_timeout"
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
index b8391d7..488547d 100644
--- a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
@@ -17,6 +17,7 @@
package android.provider.cts.contacts;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.provider.ContactsContract;
import android.test.AndroidTestCase;
@@ -53,4 +54,21 @@
intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
assertCanBeHandled(intent);
}
+
+ public void testSetDefaultAccount() {
+ Intent intent = new Intent(ContactsContract.Settings.ACTION_SET_DEFAULT_ACCOUNT);
+ PackageManager packageManager = getContext().getPackageManager();
+ List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(intent, 0);
+ assertNotNull("Missing ResolveInfo", resolveInfoList);
+ int handlerCount = 0;
+ for (ResolveInfo resolveInfo : resolveInfoList) {
+ String packageName = resolveInfo.activityInfo.packageName;
+ if (packageManager.checkPermission(
+ android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS, packageName)
+ == PackageManager.PERMISSION_GRANTED) {
+ handlerCount++;
+ }
+ }
+ assertEquals(1, handlerCount);
+ }
}
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_AllUriTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_AllUriTest.java
index dfeaad1..57c3006 100644
--- a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_AllUriTest.java
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_AllUriTest.java
@@ -35,6 +35,8 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
@LargeTest
public class ContactsContract_AllUriTest extends AndroidTestCase {
@@ -144,7 +146,7 @@
{"content://com.android.contacts/phone_lookup/XXX"},
{"content://com.android.contacts/phone_lookup_enterprise/XXX"},
{"content://com.android.contacts/aggregation_exceptions", "u"},
- {"content://com.android.contacts/settings", "ud"},
+ {"content://com.android.contacts/settings", "iud"},
{"content://com.android.contacts/status_updates", "ud"},
{"content://com.android.contacts/status_updates/1"},
{"content://com.android.contacts/search_suggest_query"},
@@ -178,8 +180,26 @@
{"content://com.android.contacts/directory_file_enterprise/XXX?directory=0", "-"},
};
+ // Contains entries for Uris that require specific values when inserting
+ private static final Map<String, ContentValues> URI_INSERT_VALUES;
+
private static final String[] ARG1 = {"-1"};
+ static {
+ URI_INSERT_VALUES = new HashMap<>();
+ ContentValues values = new ContentValues();
+ values.put(SyncState.ACCOUNT_NAME, "abc");
+ values.put(SyncState.ACCOUNT_TYPE, "def");
+ URI_INSERT_VALUES.put("content://com.android.contacts/syncstate", values);
+ URI_INSERT_VALUES.put("content://com.android.contacts/syncstate/1", values);
+ URI_INSERT_VALUES.put("content://com.android.contacts/profile/syncstate", values);
+
+ values = new ContentValues();
+ values.put(ContactsContract.Settings.ACCOUNT_NAME, "abc");
+ values.put(ContactsContract.Settings.ACCOUNT_TYPE, "def");
+ URI_INSERT_VALUES.put("content://com.android.contacts/settings", values);
+ }
+
private ContentResolver mResolver;
private ArrayList<String> mFailures;
@@ -627,15 +647,13 @@
final Uri uri = getUri(path);
cv.clear();
- if (supportsQuery(path)) {
+ if (URI_INSERT_VALUES.containsKey(path[0])) {
+ cv.putAll(URI_INSERT_VALUES.get(path[0]));
+ } else if (supportsQuery(path)) {
cv.put(getColumns(uri)[0], 1);
} else {
cv.put("_id", 1);
}
- if (uri.toString().contains("syncstate")) {
- cv.put(SyncState.ACCOUNT_NAME, "abc");
- cv.put(SyncState.ACCOUNT_TYPE, "def");
- }
checkExecutable("insert", uri, supportsInsert(path), () -> {
final Uri newUri = mResolver.insert(uri, cv);
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java
index cb3a2a6..4b98883 100644
--- a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java
@@ -142,8 +142,8 @@
DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
String[] projection = new String[] {
- ContactsContract.RawContacts.DIRTY,
- ContactsContract.RawContacts.DELETED
+ RawContacts.DIRTY,
+ RawContacts.DELETED
};
List<String[]> records = RawContactUtil.queryByContactId(mResolver, ids.mContactId,
projection);
@@ -229,8 +229,8 @@
// Assert that the non-local raw contact was marked DELETED=1
String[] projection = new String[]{
- ContactsContract.RawContacts.DIRTY,
- ContactsContract.RawContacts.DELETED
+ RawContacts.DIRTY,
+ RawContacts.DELETED
};
List<String[]> records = RawContactUtil.queryByContactId(mResolver, ids2.mContactId,
projection);
@@ -335,6 +335,20 @@
assertEquals(0, rawContact.getLong(Contacts.TIMES_CONTACTED));
}
+ /** Make sure local contacts are visible by default. */
+ public void testContactQuery_localContactVisibleByDefault() throws Exception {
+ // Raw contacts without an account specified are created in the local account
+ final TestRawContact localRawContact = mBuilder.newRawContact().insert().load();
+ final TestContact contact = localRawContact.getContact().load();
+
+ assertEquals(RawContacts.getLocalAccountName(mContext),
+ localRawContact.getString(RawContacts.ACCOUNT_NAME));
+ assertEquals(RawContacts.getLocalAccountType(mContext),
+ localRawContact.getString(RawContacts.ACCOUNT_TYPE));
+ assertNull(localRawContact.getString(RawContacts.DATA_SET));
+ assertEquals(1, contact.getLong(Contacts.IN_VISIBLE_GROUP));
+ }
+
public void testProjection() throws Exception {
final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java
index 2ec7ec8..bc6ea56 100644
--- a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java
@@ -228,7 +228,7 @@
*
* <p>See {@link #testRawContactCreate_noAccountUsesLocalAccount()}
*/
- @CddTest(requirement="3.18/C-1-1,C-1-2,C-1-3")
+ @CddTest(requirement = "3.18/C-1-1,C-1-2,C-1-3")
public void testRawContactCreate_nullAccountUsesLocalAccount() throws Exception {
// Save a raw contact using the default local account
TestRawContact rawContact = mBuilder.newRawContact()
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SettingsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SettingsTest.java
new file mode 100644
index 0000000..1adde79
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SettingsTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts.contacts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.ContentResolver;
+import android.provider.ContactsContract.Settings;
+import android.provider.ContactsContract.SimAccount;
+import android.provider.ContactsContract.SimContacts;
+import android.provider.cts.contacts.account.StaticAccountAuthenticator;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+@MediumTest
+public class ContactsContract_SettingsTest extends AndroidTestCase {
+ // Using unique account name and type because these tests may break or be broken by
+ // other tests running. No other tests should use the following accounts.
+ private static final Account ACCT_1 = new Account("test for default account1",
+ StaticAccountAuthenticator.TYPE);
+ private static final Account ACCT_2 = new Account("test for default account2",
+ StaticAccountAuthenticator.TYPE);
+
+ private static final String SIM_ACCT_NAME = "sim account name for default account test";
+ private static final String SIM_ACCT_TYPE = "sim account type for default account test";
+ private static final int SIM_SLOT_0 = 0;
+
+ private ContentResolver mResolver;
+ private AccountManager mAccountManager;
+ private Account mInitialDefaultAccount;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mResolver = getContext().getContentResolver();
+ mAccountManager = AccountManager.get(getContext());
+
+ mAccountManager.addAccountExplicitly(ACCT_1, null, null);
+ mAccountManager.addAccountExplicitly(ACCT_2, null, null);
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ SimContacts.addSimAccount(mResolver, SIM_ACCT_NAME, SIM_ACCT_TYPE, SIM_SLOT_0,
+ SimAccount.ADN_EF_TYPE);
+ });
+
+ mInitialDefaultAccount = Settings.getDefaultAccount(mResolver);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mAccountManager.removeAccount(ACCT_1, null, null);
+ mAccountManager.removeAccount(ACCT_2, null, null);
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ SimContacts.removeSimAccounts(mResolver, SIM_SLOT_0);
+ });
+
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ Settings.setDefaultAccount(mResolver, mInitialDefaultAccount);
+ });
+
+ }
+
+ /**
+ * Default account set by
+ * {@link Settings#setDefaultAccount(ContentResolver, Account)} should be
+ * returned by {@link Settings#getDefaultAccount(ContentResolver)}
+ */
+ public void testSetDefaultAccount_returnedByGetDefaultAccount() {
+ // Set default account to a system account and call get to check.
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ Settings.setDefaultAccount(mResolver, ACCT_1);
+ });
+
+ Account defaultAccount = Settings.getDefaultAccount(mResolver);
+ assertThat(defaultAccount.name).isEqualTo("test for default account1");
+ assertThat(defaultAccount.type).isEqualTo(StaticAccountAuthenticator.TYPE);
+
+ // Update default account to system account and call get to check.
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ Settings.setDefaultAccount(mResolver, ACCT_2);
+ });
+
+ defaultAccount = Settings.getDefaultAccount(mResolver);
+ assertThat(defaultAccount.name).isEqualTo("test for default account2");
+ assertThat(defaultAccount.type).isEqualTo(StaticAccountAuthenticator.TYPE);
+
+ // Update default account to NULL and check.
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ Settings.setDefaultAccount(mResolver, null);
+ });
+
+ defaultAccount = Settings.getDefaultAccount(mResolver);
+ assertThat(defaultAccount).isNull();
+
+ // Update default account to sim account and check.
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ Settings.setDefaultAccount(mResolver, new Account(SIM_ACCT_NAME, SIM_ACCT_TYPE));
+ });
+
+ defaultAccount = Settings.getDefaultAccount(mResolver);
+ assertThat(defaultAccount.name).isEqualTo(SIM_ACCT_NAME);
+ assertThat(defaultAccount.type).isEqualTo(SIM_ACCT_TYPE);
+ }
+
+ public void testSetDefaultAccount_invalidAccount() {
+ // Setting an invalid account will throw exception.
+ try {
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ Settings.setDefaultAccount(mResolver, new Account("a", "b"));
+ });
+ fail();
+ } catch (RuntimeException expected) {
+ }
+
+ Account defaultAccount = Settings.getDefaultAccount(mResolver);
+ assertThat(defaultAccount).isEqualTo(mInitialDefaultAccount);
+ }
+}
+
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index 556add2..7b59702 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -368,7 +368,6 @@
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.INFO"/>
</intent-filter>
</activity>
<!--Test for PackageManager-->
@@ -392,6 +391,7 @@
android:exported="true">
<intent-filter android:priority="-10">
<action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.INFO"/>
<category android:name="android.intent.category.HOME"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
diff --git a/tests/tests/content/AndroidTest.xml b/tests/tests/content/AndroidTest.xml
index 1b757e3..9b18f72 100644
--- a/tests/tests/content/AndroidTest.xml
+++ b/tests/tests/content/AndroidTest.xml
@@ -41,7 +41,9 @@
<option name="push" value="CtsContentLongSharedUserIdTestApp.apk->/data/local/tmp/cts/content/CtsContentLongSharedUserIdTestApp.apk" />
<option name="push" value="CtsContentMaxPackageNameTestApp.apk->/data/local/tmp/cts/content/CtsContentMaxPackageNameTestApp.apk" />
<option name="push" value="CtsContentMaxSharedUserIdTestApp.apk->/data/local/tmp/cts/content/CtsContentMaxSharedUserIdTestApp.apk" />
+ <option name="push" value="CtsContentMockLauncherTestApp.apk->/data/local/tmp/cts/content/CtsContentMockLauncherTestApp.apk" />
<option name="push" value="CtsContentLongLabelNameTestApp.apk->/data/local/tmp/cts/content/CtsContentLongLabelNameTestApp.apk" />
+ <option name="push" value="CtsSyncAccountAccessStubs.apk->/data/local/tmp/cts/content/CtsSyncAccountAccessStubs.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
@@ -101,6 +103,30 @@
<option name="push-file" key="HelloWorldResHardening_mdpi-v4.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldResHardening_mdpi-v4.apk.idsig" />
<option name="push-file" key="malformed.apk.idsig" value="/data/local/tmp/cts/content/malformed.apk.idsig" />
<option name="push-file" key="test-cert.x509.pem" value="/data/local/tmp/cts/content/test-cert.x509.pem" />
+ <option name="push-file" key="HelloWorldSdk1.apk" value="/data/local/tmp/cts/content/HelloWorldSdk1.apk" />
+ <option name="push-file" key="HelloWorldSdk1.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSdk1.apk.idsig" />
+ <option name="push-file" key="HelloWorldSdk1Updated.apk" value="/data/local/tmp/cts/content/HelloWorldSdk1Updated.apk" />
+ <option name="push-file" key="HelloWorldSdk1Updated.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSdk1Updated.apk.idsig" />
+ <option name="push-file" key="HelloWorldSdk1MajorVersion2.apk" value="/data/local/tmp/cts/content/HelloWorldSdk1MajorVersion2.apk" />
+ <option name="push-file" key="HelloWorldSdk1MajorVersion2.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSdk1MajorVersion2.apk.idsig" />
+ <option name="push-file" key="HelloWorldSdk1DifferentSigner.apk" value="/data/local/tmp/cts/content/HelloWorldSdk1DifferentSigner.apk" />
+ <option name="push-file" key="HelloWorldSdk1DifferentSigner.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSdk1DifferentSigner.apk.idsig" />
+ <option name="push-file" key="HelloWorldSdk2.apk" value="/data/local/tmp/cts/content/HelloWorldSdk2.apk" />
+ <option name="push-file" key="HelloWorldSdk2.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSdk2.apk.idsig" />
+ <option name="push-file" key="HelloWorldSdk2Updated.apk" value="/data/local/tmp/cts/content/HelloWorldSdk2Updated.apk" />
+ <option name="push-file" key="HelloWorldSdk2Updated.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSdk2Updated.apk.idsig" />
+ <option name="push-file" key="HelloWorldSdk3UsingSdk1.apk" value="/data/local/tmp/cts/content/HelloWorldSdk3UsingSdk1.apk" />
+ <option name="push-file" key="HelloWorldSdk3UsingSdk1.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSdk3UsingSdk1.apk.idsig" />
+ <option name="push-file" key="HelloWorldSdk3UsingSdk1And2.apk" value="/data/local/tmp/cts/content/HelloWorldSdk3UsingSdk1And2.apk" />
+ <option name="push-file" key="HelloWorldSdk3UsingSdk1And2.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSdk3UsingSdk1And2.apk.idsig" />
+ <option name="push-file" key="HelloWorldUsingSdk1.apk" value="/data/local/tmp/cts/content/HelloWorldUsingSdk1.apk" />
+ <option name="push-file" key="HelloWorldUsingSdk1.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldUsingSdk1.apk.idsig" />
+ <option name="push-file" key="HelloWorldUsingSdk1And2.apk" value="/data/local/tmp/cts/content/HelloWorldUsingSdk1And2.apk" />
+ <option name="push-file" key="HelloWorldUsingSdk1And2.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldUsingSdk1And2.apk.idsig" />
+ <option name="push-file" key="HelloWorldUsingSdk3.apk" value="/data/local/tmp/cts/content/HelloWorldUsingSdk3.apk" />
+ <option name="push-file" key="HelloWorldUsingSdk3.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldUsingSdk3.apk.idsig" />
+ <option name="push-file" key="HelloWorldNoAppStorage.apk" value="/data/local/tmp/cts/content/HelloWorldNoAppStorage.apk" />
+ <option name="push-file" key="HelloWorldNoAppStorage.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldNoAppStorage.apk.idsig" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
@@ -110,6 +136,9 @@
<option name="test-file-name" value="CtsContentPartiallyDirectBootAwareTestApp.apk" />
<option name="test-file-name" value="CtsSyncAccountAccessStubs.apk" />
<option name="test-file-name" value="CtsBinderPermissionTestService.apk" />
+ <option name="test-file-name" value="CtsIntentResolutionTestApp.apk" />
+ <option name="test-file-name" value="CtsIntentResolutionTestAppApi30.apk" />
+ <option name="test-file-name" value="CtsContentNoApplicationTestApp.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/content/BinderPermissionTestService/Android.bp b/tests/tests/content/BinderPermissionTestService/Android.bp
index 928e532..8ba2432 100644
--- a/tests/tests/content/BinderPermissionTestService/Android.bp
+++ b/tests/tests/content/BinderPermissionTestService/Android.bp
@@ -27,6 +27,7 @@
"aidl/**/I*.aidl",
],
test_suites: [
+ "mts",
"cts",
"general-tests",
],
diff --git a/tests/tests/content/DirectBootUnawareTestApp/Android.bp b/tests/tests/content/DirectBootUnawareTestApp/Android.bp
index 4a1a9bb..8c35eb4 100644
--- a/tests/tests/content/DirectBootUnawareTestApp/Android.bp
+++ b/tests/tests/content/DirectBootUnawareTestApp/Android.bp
@@ -22,6 +22,7 @@
sdk_version: "current",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
diff --git a/tests/tests/content/HelloWorldApp/Android.bp b/tests/tests/content/HelloWorldApp/Android.bp
index e3c2de5..e3496a3 100644
--- a/tests/tests/content/HelloWorldApp/Android.bp
+++ b/tests/tests/content/HelloWorldApp/Android.bp
@@ -42,6 +42,7 @@
srcs: ["src5/**/*.java"],
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
@@ -56,6 +57,7 @@
manifest: "AndroidManifestProfileable.xml",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"vts10",
"general-tests",
@@ -70,6 +72,7 @@
srcs: ["src7/**/*.java"],
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
@@ -105,6 +108,7 @@
],
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
@@ -120,6 +124,200 @@
manifest: "AndroidManifestShell.xml",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+ name: "HelloWorldSdk1",
+ defaults: ["hello_world_defaults"],
+ srcs: ["sdk1/**/*.java"],
+ manifest: "AndroidManifestSdk1.xml",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "mts",
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+ name: "HelloWorldSdk1Updated",
+ defaults: ["hello_world_defaults"],
+ srcs: ["sdk1/**/*.java"],
+ manifest: "AndroidManifestSdk1Updated.xml",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "mts",
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+ name: "HelloWorldSdk1MajorVersion2",
+ defaults: ["hello_world_defaults"],
+ srcs: ["sdk1/**/*.java"],
+ manifest: "AndroidManifestSdk1MajorVersion2.xml",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "mts",
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+ name: "HelloWorldSdk1DifferentSigner",
+ defaults: ["hello_world_defaults"],
+ srcs: ["sdk1/**/*.java"],
+ manifest: "AndroidManifestSdk1.xml",
+ certificate: ":cts-testkey1",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "mts",
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+ name: "HelloWorldSdk2",
+ defaults: ["hello_world_defaults"],
+ srcs: ["sdk2/**/*.java"],
+ manifest: "AndroidManifestSdk2.xml",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "mts",
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+ name: "HelloWorldSdk2Updated",
+ defaults: ["hello_world_defaults"],
+ srcs: ["sdk2/**/*.java"],
+ manifest: "AndroidManifestSdk2Updated.xml",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "mts",
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+ name: "HelloWorldSdk3UsingSdk1",
+ defaults: ["hello_world_defaults"],
+ srcs: ["sdk3/**/*.java"],
+ manifest: "AndroidManifestSdk3UsingSdk1.xml",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "mts",
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+ name: "HelloWorldSdk3UsingSdk1And2",
+ defaults: ["hello_world_defaults"],
+ srcs: ["sdk3/**/*.java"],
+ manifest: "AndroidManifestSdk3UsingSdk1And2.xml",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "mts",
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+ name: "HelloWorldUsingSdk1",
+ defaults: ["hello_world_defaults"],
+ srcs: ["sdk_user/**/*.java"],
+ manifest: "AndroidManifestUsingSdk1.xml",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "mts",
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+ name: "HelloWorldUsingSdk1And2",
+ defaults: ["hello_world_defaults"],
+ srcs: ["sdk_user/**/*.java"],
+ manifest: "AndroidManifestUsingSdk1And2.xml",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "mts",
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+ name: "HelloWorldUsingSdk3",
+ defaults: ["hello_world_defaults"],
+ srcs: ["sdk_user/**/*.java"],
+ manifest: "AndroidManifestUsingSdk3.xml",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "mts",
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+ name: "HelloWorldNoAppStorage",
+ defaults: ["hello_world_defaults"],
+ srcs: ["src5/**/*.java"],
+ manifest: "AndroidManifestNoAppStorage.xml",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "mts",
"cts",
"vts10",
"general-tests",
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestNoAppStorage.xml b/tests/tests/content/HelloWorldApp/AndroidManifestNoAppStorage.xml
new file mode 100644
index 0000000..ff95116
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestNoAppStorage.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.helloworld">
+
+ <application android:allowBackup="true"
+ android:debuggable="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme"
+ android:forceQueryable="true">
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme.NoActionBar"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <property android:name="android.internal.PROPERTY_NO_APP_DATA_STORAGE"
+ android:value="true" />
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+ </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestSdk1.xml b/tests/tests/content/HelloWorldApp/AndroidManifestSdk1.xml
new file mode 100644
index 0000000..3c10917
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestSdk1.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.test.sdk1_1"
+ android:versionCode="2">
+
+ <application android:allowBackup="true"
+ android:debuggable="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme.NoActionBar"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <sdk-library android:name="com.test.sdk1" android:versionMajor="1" />
+
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+
+ </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestSdk1MajorVersion2.xml b/tests/tests/content/HelloWorldApp/AndroidManifestSdk1MajorVersion2.xml
new file mode 100644
index 0000000..b2e6527
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestSdk1MajorVersion2.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.test.sdk1_2"
+ android:versionCode="2">
+
+ <application android:allowBackup="true"
+ android:debuggable="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme.NoActionBar"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <sdk-library android:name="com.test.sdk1" android:versionMajor="2" />
+
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+
+ </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestSdk1Updated.xml b/tests/tests/content/HelloWorldApp/AndroidManifestSdk1Updated.xml
new file mode 100644
index 0000000..7946cbd
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestSdk1Updated.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.test.sdk1_1"
+ android:versionCode="1">
+
+ <application android:allowBackup="true"
+ android:debuggable="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme.NoActionBar"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <sdk-library android:name="com.test.sdk1" android:versionMajor="1" />
+
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+
+ </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestSdk2.xml b/tests/tests/content/HelloWorldApp/AndroidManifestSdk2.xml
new file mode 100644
index 0000000..4e5f4dc
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestSdk2.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.test.sdk2_2"
+ android:versionCode="1">
+
+ <application android:allowBackup="true"
+ android:debuggable="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme.NoActionBar"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <sdk-library android:name="com.test.sdk2" android:versionMajor="2" />
+
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+
+ </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestSdk2Updated.xml b/tests/tests/content/HelloWorldApp/AndroidManifestSdk2Updated.xml
new file mode 100644
index 0000000..7ac1f2f
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestSdk2Updated.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.test.sdk2_2"
+ android:versionCode="2">
+
+ <application android:allowBackup="true"
+ android:debuggable="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme.NoActionBar"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <sdk-library android:name="com.test.sdk2" android:versionMajor="2" />
+
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+
+ </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestSdk3UsingSdk1.xml b/tests/tests/content/HelloWorldApp/AndroidManifestSdk3UsingSdk1.xml
new file mode 100644
index 0000000..cb57d19
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestSdk3UsingSdk1.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.test.sdk3_3"
+ android:versionCode="10">
+
+ <application android:allowBackup="true"
+ android:debuggable="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme.NoActionBar"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <sdk-library android:name="com.test.sdk3" android:versionMajor="3" />
+
+ <uses-sdk-library android:name="com.test.sdk1" android:versionMajor="1" android:certDigest="" />
+
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+
+ </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestSdk3UsingSdk1And2.xml b/tests/tests/content/HelloWorldApp/AndroidManifestSdk3UsingSdk1And2.xml
new file mode 100644
index 0000000..b8a8d96
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestSdk3UsingSdk1And2.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.test.sdk3_3"
+ android:versionCode="5">
+
+ <application android:allowBackup="true"
+ android:debuggable="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme.NoActionBar"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <sdk-library android:name="com.test.sdk3" android:versionMajor="3" />
+
+ <uses-sdk-library android:name="com.test.sdk1" android:versionMajor="1" android:certDigest="" />
+ <uses-sdk-library android:name="com.test.sdk2" android:versionMajor="2" android:certDigest="" />
+
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+
+ </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk1.xml b/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk1.xml
new file mode 100644
index 0000000..d24276b
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk1.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.test.sdk.user">
+
+ <application android:allowBackup="true"
+ android:debuggable="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme.NoActionBar"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <uses-sdk-library android:name="com.test.sdk1" android:versionMajor="1" android:certDigest="" />
+
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+
+ </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk1And2.xml b/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk1And2.xml
new file mode 100644
index 0000000..2e60a85
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk1And2.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.test.sdk.user">
+
+ <application android:allowBackup="true"
+ android:debuggable="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme.NoActionBar"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <uses-sdk-library android:name="com.test.sdk1" android:versionMajor="1" android:certDigest="" />
+ <uses-sdk-library android:name="com.test.sdk2" android:versionMajor="2" android:certDigest="" />
+
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+
+ </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk3.xml b/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk3.xml
new file mode 100644
index 0000000..8601b88
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk3.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.test.sdk.user">
+
+ <application android:allowBackup="true"
+ android:debuggable="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme.NoActionBar"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <uses-sdk-library android:name="com.test.sdk3" android:versionMajor="3" android:certDigest="" />
+
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+
+ </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/sdk1/com/example/helloworld/MainActivity.java b/tests/tests/content/HelloWorldApp/sdk1/com/example/helloworld/MainActivity.java
new file mode 100644
index 0000000..3104534
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/sdk1/com/example/helloworld/MainActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.test.sdk1_1;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class MainActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ System.exit(5);
+ }
+}
diff --git a/tests/tests/content/HelloWorldApp/sdk2/com/example/helloworld/MainActivity.java b/tests/tests/content/HelloWorldApp/sdk2/com/example/helloworld/MainActivity.java
new file mode 100644
index 0000000..f060455
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/sdk2/com/example/helloworld/MainActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.test.sdk2_2;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class MainActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ System.exit(5);
+ }
+}
diff --git a/tests/tests/content/HelloWorldApp/sdk3/com/example/helloworld/MainActivity.java b/tests/tests/content/HelloWorldApp/sdk3/com/example/helloworld/MainActivity.java
new file mode 100644
index 0000000..5945d02
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/sdk3/com/example/helloworld/MainActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.test.sdk3_3;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class MainActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ System.exit(5);
+ }
+}
diff --git a/tests/tests/content/HelloWorldApp/sdk_user/com/example/helloworld/MainActivity.java b/tests/tests/content/HelloWorldApp/sdk_user/com/example/helloworld/MainActivity.java
new file mode 100644
index 0000000..4ffb930
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/sdk_user/com/example/helloworld/MainActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.test.sdk.user;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class MainActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ System.exit(5);
+ }
+}
diff --git a/tests/tests/content/IntentResolutionTestApp/Android.bp b/tests/tests/content/IntentResolutionTestApp/Android.bp
new file mode 100644
index 0000000..16159cf
--- /dev/null
+++ b/tests/tests/content/IntentResolutionTestApp/Android.bp
@@ -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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsIntentResolutionTestApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ min_sdk_version: "29",
+}
+
+// Same app but with lower target SDK version and different package_name
+android_test_helper_app {
+ name: "CtsIntentResolutionTestAppApi30",
+ package_name: "android.content.cts.IntentResolutionTestApi30",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ target_sdk_version: "30",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ min_sdk_version: "29",
+}
diff --git a/tests/tests/content/IntentResolutionTestApp/AndroidManifest.xml b/tests/tests/content/IntentResolutionTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..ac50cb1
--- /dev/null
+++ b/tests/tests/content/IntentResolutionTestApp/AndroidManifest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.cts.IntentResolutionTest" >
+ <application android:hasCode="false">
+ <activity android:name="android.content.pm.cts.TestPmActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.RESOLUTION_TEST"/>
+ <action android:name="android.intent.action.SELECTORTEST"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.RESOLUTION_TEST2"/>
+ <data android:scheme="http" android:mimeType="*/*" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.RESOLUTION_TEST2"/>
+ <data android:scheme="content" android:mimeType="text/plain" />
+ </intent-filter>
+ </activity>
+ <service android:name="android.content.pm.cts.TestPmService"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.RESOLUTION_TEST"/>
+ <action android:name="android.intent.action.SELECTORTEST"/>
+ </intent-filter>
+ </service>
+ <receiver android:name="android.content.pm.cts.PmTestReceiver"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.RESOLUTION_TEST"/>
+ <action android:name="android.intent.action.SELECTORTEST"/>
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
diff --git a/tests/tests/content/MockLauncherApp/Android.bp b/tests/tests/content/MockLauncherApp/Android.bp
new file mode 100644
index 0000000..050dab1
--- /dev/null
+++ b/tests/tests/content/MockLauncherApp/Android.bp
@@ -0,0 +1,29 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsContentMockLauncherTestApp",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.java"],
+ sdk_version: "current",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
diff --git a/tests/tests/content/MockLauncherApp/AndroidManifest.xml b/tests/tests/content/MockLauncherApp/AndroidManifest.xml
new file mode 100644
index 0000000..cfac8f4
--- /dev/null
+++ b/tests/tests/content/MockLauncherApp/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.cts.mocklauncherapp">
+ <application android:resetEnabledSettingsOnAppDataCleared="true">
+ <uses-library android:name="android.test.runner"/>
+
+ <activity android:name="android.content.cts.mocklauncherapp.Launcher"
+ android:enabled="true"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.HOME"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+
+ <!-- Tests for attribute of resetEnabledSetting -->
+ <activity android:name=".MockActivity" android:exported="false"
+ android:enabled="false" />
+ <receiver android:name=".MockReceiver" android:exported="false"
+ android:enabled="false" />
+ <service android:name=".MockService" android:exported="false"
+ android:enabled="false" />
+ <provider android:authorities="android.content.cts.mocklauncherapp"
+ android:name=".MockProvider" android:exported="false"
+ android:enabled="false" />
+ </application>
+</manifest>
diff --git a/tests/tests/content/MockLauncherApp/src/android/content/cts/mocklauncherapp/Launcher.java b/tests/tests/content/MockLauncherApp/src/android/content/cts/mocklauncherapp/Launcher.java
new file mode 100644
index 0000000..ddd30e1
--- /dev/null
+++ b/tests/tests/content/MockLauncherApp/src/android/content/cts/mocklauncherapp/Launcher.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.cts.mocklauncherapp;
+
+import android.app.Activity;
+
+public class Launcher extends Activity {
+}
diff --git a/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp b/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp
index c24d69a..c211c0c 100644
--- a/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp
+++ b/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp
@@ -22,6 +22,7 @@
sdk_version: "current",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
diff --git a/tests/tests/content/SyncAccountAccessStubs/Android.bp b/tests/tests/content/SyncAccountAccessStubs/Android.bp
index e8904d7..9f1e9ab 100644
--- a/tests/tests/content/SyncAccountAccessStubs/Android.bp
+++ b/tests/tests/content/SyncAccountAccessStubs/Android.bp
@@ -25,6 +25,7 @@
srcs: ["src/**/*.java"],
sdk_version: "current",
test_suites: [
+ "mts",
"cts",
"general-tests",
],
diff --git a/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml b/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
index 8423733..1712e43 100644
--- a/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
+++ b/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
@@ -18,6 +18,10 @@
package="com.android.cts.stub">
<application>
+ <activity android:name=".StubActivity" android:exported="true" />
+
+ <service android:name=".StubService" android:exported="true" />
+
<service android:name=".StubAuthenticator"
android:exported="true">
<intent-filter>
diff --git a/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubService.java b/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubService.java
new file mode 100644
index 0000000..29face6
--- /dev/null
+++ b/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubService.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.stub;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+public class StubService extends Service {
+ private Binder mBinder = new Binder();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+}
diff --git a/tests/tests/content/TEST_MAPPING b/tests/tests/content/TEST_MAPPING
index ed0ac34..7f588e4 100644
--- a/tests/tests/content/TEST_MAPPING
+++ b/tests/tests/content/TEST_MAPPING
@@ -1,6 +1,19 @@
{
"presubmit": [
{
+ "name": "FrameworksCoreTests",
+ "options": [
+ {
+ "include-filter": "android.content.pm.PackageManagerTests"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.Suppress"
+ }
+ ]
+ }
+ ],
+ "presubmit-large": [
+ {
"name": "CtsContentTestCases",
"options": [
{
@@ -13,17 +26,6 @@
"include-filter": "android.content.pm.cts"
}
]
- },
- {
- "name": "FrameworksCoreTests",
- "options": [
- {
- "include-filter": "android.content.pm.PackageManagerTests"
- },
- {
- "exclude-annotation": "androidx.test.filters.Suppress"
- }
- ]
}
]
}
diff --git a/tests/tests/content/emptytestapp/Android.bp b/tests/tests/content/emptytestapp/Android.bp
index f66ccdf..bfcb0c6 100644
--- a/tests/tests/content/emptytestapp/Android.bp
+++ b/tests/tests/content/emptytestapp/Android.bp
@@ -22,10 +22,11 @@
sdk_version: "current",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
- min_sdk_version : "29"
+ min_sdk_version: "29",
}
android_test_helper_app {
@@ -35,10 +36,11 @@
manifest: "AndroidManifestLongPackageName.xml",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
- aaptflags: ["--warn-manifest-validation"]
+ aaptflags: ["--warn-manifest-validation"],
}
android_test_helper_app {
@@ -48,10 +50,11 @@
manifest: "AndroidManifestLongSharedUserId.xml",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
- aaptflags: ["--warn-manifest-validation"]
+ aaptflags: ["--warn-manifest-validation"],
}
android_test_helper_app {
@@ -61,6 +64,7 @@
manifest: "AndroidManifestMaxPackageName.xml",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
@@ -73,6 +77,7 @@
manifest: "AndroidManifestMaxSharedUserId.xml",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
@@ -88,5 +93,21 @@
"cts",
"general-tests",
],
- min_sdk_version : "29"
+ min_sdk_version: "29",
+}
+
+android_test_helper_app {
+ name: "CtsContentNoApplicationTestApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ manifest: "AndroidManifestEmpty.xml",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ // using 22 (< 23) to avoid soong adds application tag with attr extract-native-libs
+ min_sdk_version: "22",
+ // using 29 (< 30) to allow install an app without application tag
+ target_sdk_version: "29",
}
diff --git a/tests/tests/content/emptytestapp/AndroidManifestEmpty.xml b/tests/tests/content/emptytestapp/AndroidManifestEmpty.xml
new file mode 100644
index 0000000..ee6d279
--- /dev/null
+++ b/tests/tests/content/emptytestapp/AndroidManifestEmpty.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.cts.emptytestapp.stub" >
+</manifest>
diff --git a/tests/tests/content/pm/SecureFrp/src/com/android/tests/securefrpinstall/SecureFrpInstallTest.java b/tests/tests/content/pm/SecureFrp/src/com/android/tests/securefrpinstall/SecureFrpInstallTest.java
index e2712e6..f715c2c 100644
--- a/tests/tests/content/pm/SecureFrp/src/com/android/tests/securefrpinstall/SecureFrpInstallTest.java
+++ b/tests/tests/content/pm/SecureFrp/src/com/android/tests/securefrpinstall/SecureFrpInstallTest.java
@@ -64,12 +64,12 @@
}
private static void assertInstalled() throws Exception {
- sPackageManager.getPackageInfo(TestApp.A, 0);
+ sPackageManager.getPackageInfo(TestApp.A, PackageManager.PackageInfoFlags.of(0));
}
private static void assertNotInstalled() {
try {
- sPackageManager.getPackageInfo(TestApp.A, 0);
+ sPackageManager.getPackageInfo(TestApp.A, PackageManager.PackageInfoFlags.of(0));
fail("Package should not be installed");
} catch (PackageManager.NameNotFoundException expected) {
}
diff --git a/tests/tests/content/res/xml/file_paths.xml b/tests/tests/content/res/xml/file_paths.xml
index e8d2861..0653305 100644
--- a/tests/tests/content/res/xml/file_paths.xml
+++ b/tests/tests/content/res/xml/file_paths.xml
@@ -4,4 +4,5 @@
<!-- Specify the storage area and path used in file provider. -->
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="debug" path="debug/"/>
-</paths>
\ No newline at end of file
+ <files-path name="files" path="/" />
+</paths>
diff --git a/tests/tests/content/src/android/content/cts/ClipboardAutoClearTest.java b/tests/tests/content/src/android/content/cts/ClipboardAutoClearTest.java
new file mode 100644
index 0000000..ba275e2
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/ClipboardAutoClearTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.cts;
+
+import static com.android.server.clipboard.ClipboardService.PROPERTY_AUTO_CLEAR_ENABLED;
+import static com.android.server.clipboard.ClipboardService.PROPERTY_AUTO_CLEAR_TIMEOUT;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.provider.DeviceConfig;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.clipboard.ClipboardService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ClipboardAutoClearTest {
+ private final Context mContext = InstrumentationRegistry.getTargetContext();
+ private ClipboardManager mClipboardManager;
+ private UiDevice mUiDevice;
+ private final int mLatency = 100;
+ private final String mTestLatency = Integer.toString(mLatency);
+ private static final String LOG_TAG = "ClipboardAutoClearTest";
+ private final long mDefaultTimeout = 3600000;
+
+
+ @Before
+ public void setUp() throws Exception {
+ assumeTrue("Skipping Test: Wear-Os does not support ClipboardService",
+ hasAutoFillFeature());
+ mClipboardManager = mContext.getSystemService(ClipboardManager.class);
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation().adoptShellPermissionIdentity();
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mUiDevice.wakeUp();
+ launchActivity(MockActivity.class);
+ }
+
+ private void launchActivity(Class<? extends Activity> clazz) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(mContext.getPackageName(), clazz.getName());
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ mUiDevice.wait(Until.hasObject(By.pkg(clazz.getPackageName())), 15000);
+ }
+
+ @After
+ public void cleanUp() {
+ mClipboardManager.clearPrimaryClip();
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+
+ @Test
+ public void testAutoClearEnabledDefault() {
+ String enabled = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CLIPBOARD,
+ PROPERTY_AUTO_CLEAR_ENABLED);
+
+ if (enabled != null) {
+ DeviceConfig.deleteProperty(DeviceConfig.NAMESPACE_CLIPBOARD,
+ PROPERTY_AUTO_CLEAR_ENABLED);
+ }
+
+ ClipboardManager clipboardManager = mClipboardManager;
+ clipboardManager.setPrimaryClip(
+ ClipData.newPlainText("Test label", "Test string"));
+ assertTrue(clipboardManager.hasPrimaryClip());
+
+ try {
+ Thread.sleep(mLatency * 10);
+ } catch (InterruptedException e) {
+ Log.w(LOG_TAG, e);
+ }
+
+ assertTrue(clipboardManager.hasPrimaryClip());
+ clipboardManager.clearPrimaryClip();
+
+ if (enabled != null) {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CLIPBOARD,
+ PROPERTY_AUTO_CLEAR_ENABLED, enabled, false);
+ }
+ }
+
+ @Test
+ public void testAutoClearJob() throws Exception {
+ String enabled = getAndSetProperty(
+ PROPERTY_AUTO_CLEAR_ENABLED, "true");
+ String latency = getAndSetProperty(
+ PROPERTY_AUTO_CLEAR_TIMEOUT, mTestLatency);
+
+ ClipboardManager clipboardManager = mClipboardManager;
+ clipboardManager.setPrimaryClip(
+ ClipData.newPlainText("Test label", "Test string"));
+
+ assertTrue(clipboardManager.hasPrimaryClip());
+
+ retryUntil(() -> !clipboardManager.hasPrimaryClip(), "Auto clear did not run",
+ mLatency / 10);
+
+ getAndSetProperty(PROPERTY_AUTO_CLEAR_ENABLED, enabled);
+ getAndSetProperty(PROPERTY_AUTO_CLEAR_TIMEOUT, latency);
+
+ clipboardManager.clearPrimaryClip();
+ }
+
+ @Test
+ public void testDefaultAutoClearDuration() {
+ assertEquals(mDefaultTimeout, ClipboardService.DEFAULT_CLIPBOARD_TIMEOUT_MILLIS);
+ }
+
+ /**
+ * Sets new value for clipboard auto clear property
+ *
+ * @return old value for the property
+ */
+ private String getAndSetProperty(String property, String newPropertyValue) {
+ String originalPropertyValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CLIPBOARD,
+ property);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CLIPBOARD, property,
+ newPropertyValue, false);
+ return originalPropertyValue;
+ }
+
+ private boolean hasAutoFillFeature() {
+ return mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOFILL);
+ }
+}
diff --git a/tests/tests/content/src/android/content/cts/ContentResolverTest.java b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
index 1a719eb..71bffc7 100644
--- a/tests/tests/content/src/android/content/cts/ContentResolverTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
@@ -20,6 +20,8 @@
import static android.content.ContentResolver.NOTIFY_UPDATE;
import android.accounts.Account;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentResolver.MimeTypeInfo;
@@ -38,6 +40,7 @@
import android.os.OperationCanceledException;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
import android.util.Log;
@@ -45,6 +48,8 @@
import androidx.test.InstrumentationRegistry;
import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import java.io.File;
@@ -1182,6 +1187,105 @@
//expected.
}
}
+ // Tests registerContentObserverForAllUsers without INTERACT_ACROSS_USERS_FULL: verify
+ // SecurityException.
+ public void testRegisterContentObserverForAllUsersWithoutPermission() {
+ final MockContentObserver mco = new MockContentObserver();
+ try {
+ mContentResolver.registerContentObserverAsUser(TABLE1_URI, true, mco, UserHandle.ALL);
+ fail("testRegisterContentObserverForAllUsers: "
+ + "SecurityException expected on testRegisterContentObserverForAllUsers");
+ } catch (SecurityException se) {
+ // expected
+ }
+ }
+
+ public void testRegisterContentObserverAsUser() {
+ final MockContentObserver mco = new MockContentObserver();
+
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+ mContentResolver,
+ (cr) -> cr.registerContentObserverAsUser(TABLE1_URI, true, mco, mContext.getUser())
+ );
+ assertFalse(mco.hadOnChanged());
+
+ ContentValues values = new ContentValues();
+ values.put(COLUMN_KEY_NAME, "key10");
+ values.put(COLUMN_VALUE_NAME, 10);
+ mContentResolver.update(TABLE1_URI, values, null, null);
+ new PollingCheck() {
+ @Override
+ protected boolean check() {
+ return mco.hadOnChanged();
+ }
+ }.run();
+
+ mco.reset();
+ mContentResolver.unregisterContentObserver(mco);
+ assertFalse(mco.hadOnChanged());
+ mContentResolver.update(TABLE1_URI, values, null, null);
+
+ assertFalse(mco.hadOnChanged());
+ }
+
+ public void testRegisterContentObserverForAllUsers() {
+ final MockContentObserver mco = new MockContentObserver();
+
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+ mContentResolver,
+ (cr) -> cr.registerContentObserverAsUser(TABLE1_URI, true, mco, UserHandle.ALL)
+ );
+ assertFalse(mco.hadOnChanged());
+
+ ContentValues values = new ContentValues();
+ values.put(COLUMN_KEY_NAME, "key10");
+ values.put(COLUMN_VALUE_NAME, 10);
+ mContentResolver.update(TABLE1_URI, values, null, null);
+ new PollingCheck() {
+ @Override
+ protected boolean check() {
+ return mco.hadOnChanged();
+ }
+ }.run();
+
+ mco.reset();
+ mContentResolver.unregisterContentObserver(mco);
+ assertFalse(mco.hadOnChanged());
+ mContentResolver.update(TABLE1_URI, values, null, null);
+
+ assertFalse(mco.hadOnChanged());
+
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+ mContentResolver,
+ (cr) -> cr.registerContentObserverAsUser(null, false, mco, UserHandle.ALL)
+ );
+ fail("did not throw NullPointerException or IllegalArgumentException when uri is null"
+ + ".");
+ } catch (NullPointerException e) {
+ //expected.
+ } catch (IllegalArgumentException e) {
+ // also expected
+ }
+
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+ mContentResolver,
+ (cr) -> cr.registerContentObserverAsUser(TABLE1_URI, false, null,
+ UserHandle.ALL)
+ );
+ fail("did not throw NullPointerException when register null content observer.");
+ } catch (NullPointerException e) {
+ //expected.
+ }
+
+ try {
+ mContentResolver.unregisterContentObserver(null);
+ fail("did not throw NullPointerException when unregister null content observer.");
+ } catch (NullPointerException e) {
+ //expected.
+ }
+ }
public void testRegisterContentObserverDescendantBehavior() throws Exception {
final MockContentObserver mco1 = new MockContentObserver();
@@ -1230,6 +1334,59 @@
assertFalse(mco2.hadOnChanged());
}
+ public void testRegisterContentObserverForAllUsersDescendantBehavior() throws Exception {
+ final MockContentObserver mco1 = new MockContentObserver();
+ final MockContentObserver mco2 = new MockContentObserver();
+
+ // Register one content observer with notifyDescendants set to false, and
+ // another with true.
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+ mContentResolver,
+ (cr) -> cr.registerContentObserverAsUser(LEVEL2_URI, false, mco1, UserHandle.ALL)
+ );
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+ mContentResolver,
+ (cr) -> cr.registerContentObserverAsUser(LEVEL2_URI, true, mco2, UserHandle.ALL)
+ );
+
+ // Initially nothing has happened.
+ assertFalse(mco1.hadOnChanged());
+ assertFalse(mco2.hadOnChanged());
+
+ // Fire a change with the exact URI.
+ // Should signal both observers due to exact match, notifyDescendants doesn't matter.
+ mContentResolver.notifyChange(LEVEL2_URI, null);
+ Thread.sleep(200);
+ assertTrue(mco1.hadOnChanged());
+ assertTrue(mco2.hadOnChanged());
+ mco1.reset();
+ mco2.reset();
+
+ // Fire a change with a descendant URI.
+ // Should only signal observer with notifyDescendants set to true.
+ mContentResolver.notifyChange(LEVEL3_URI, null);
+ Thread.sleep(200);
+ assertFalse(mco1.hadOnChanged());
+ assertTrue(mco2.hadOnChanged());
+ mco2.reset();
+
+ // Fire a change with an ancestor URI.
+ // Should signal both observers due to ancestry, notifyDescendants doesn't matter.
+ mContentResolver.notifyChange(LEVEL1_URI, null);
+ Thread.sleep(200);
+ assertTrue(mco1.hadOnChanged());
+ assertTrue(mco2.hadOnChanged());
+ mco1.reset();
+ mco2.reset();
+
+ // Fire a change with an unrelated URI.
+ // Should signal neither observer.
+ mContentResolver.notifyChange(TABLE1_URI, null);
+ Thread.sleep(200);
+ assertFalse(mco1.hadOnChanged());
+ assertFalse(mco2.hadOnChanged());
+ }
+
public void testNotifyChange1() {
final MockContentObserver mco = new MockContentObserver();
@@ -1489,17 +1646,27 @@
public final boolean selfChange;
public final Iterable<Uri> uris;
public final int flags;
+ @UserIdInt
+ public final int userId;
public Change(boolean selfChange, Iterable<Uri> uris, int flags) {
this.selfChange = selfChange;
this.uris = uris;
this.flags = flags;
+ this.userId = -1;
+ }
+
+ public Change(boolean selfChange, Iterable<Uri> uris, int flags, @UserIdInt int userId) {
+ this.selfChange = selfChange;
+ this.uris = uris;
+ this.flags = flags;
+ this.userId = userId;
}
@Override
public String toString() {
- return String.format("onChange(%b, %s, %d)",
- selfChange, asSet(uris).toString(), flags);
+ return String.format("onChange(%b, %s, %d, %d)",
+ selfChange, asSet(uris).toString(), flags, userId);
}
@Override
@@ -1508,7 +1675,7 @@
final Change change = (Change) other;
return change.selfChange == selfChange &&
Objects.equals(asSet(change.uris), asSet(uris)) &&
- change.flags == flags;
+ change.flags == flags && change.userId == userId;
} else {
return false;
}
@@ -1536,11 +1703,13 @@
@Override
public synchronized void onChange(boolean selfChange, Collection<Uri> uris, int flags) {
- final Change change = new Change(selfChange, uris, flags);
- Log.v(TAG, change.toString());
+ doOnChangeLocked(selfChange, uris, flags, /*userId=*/ -1);
+ }
- mHadOnChanged = true;
- mChanges.add(change);
+ @Override
+ public synchronized void onChange(boolean selfChange, @NonNull Collection<Uri> uris,
+ @ContentResolver.NotifyFlags int flags, UserHandle user) {
+ doOnChangeLocked(selfChange, uris, flags, user.getIdentifier());
}
public synchronized boolean hadOnChanged() {
@@ -1554,5 +1723,15 @@
public synchronized boolean hadChanges(Collection<Change> changes) {
return mChanges.containsAll(changes);
}
+
+ @GuardedBy("this")
+ private void doOnChangeLocked(boolean selfChange, @NonNull Collection<Uri> uris,
+ @ContentResolver.NotifyFlags int flags, @UserIdInt int userId) {
+ final Change change = new Change(selfChange, uris, flags, userId);
+ Log.v(TAG, change.toString());
+
+ mHadOnChanged = true;
+ mChanges.add(change);
+ }
}
}
diff --git a/tests/tests/content/src/android/content/cts/ContextTest.java b/tests/tests/content/src/android/content/cts/ContextTest.java
index a32a6c1..b742cc9 100644
--- a/tests/tests/content/src/android/content/cts/ContextTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextTest.java
@@ -23,6 +23,7 @@
import android.app.Activity;
import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
import android.app.Instrumentation;
import android.app.WallpaperManager;
import android.content.ActivityNotFoundException;
@@ -59,6 +60,7 @@
import android.platform.test.annotations.AppModeFull;
import android.preference.PreferenceManager;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.Suppress;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
@@ -114,6 +116,13 @@
private static final int BROADCAST_TIMEOUT = 10000;
private static final int ROOT_UID = 0;
+ /**
+ * Shell command to broadcast {@link ResultReceiver#MOCK_ACTION} as an external app.
+ */
+ private static final String EXTERNAL_APP_BROADCAST_COMMAND =
+ "am broadcast -a " + ResultReceiver.MOCK_ACTION + " -f "
+ + Intent.FLAG_RECEIVER_FOREGROUND;
+
private Object mLockObj;
private ArrayList<BroadcastReceiver> mRegisteredReceiverList;
@@ -595,7 +604,14 @@
}
private void registerBroadcastReceiver(BroadcastReceiver receiver, IntentFilter filter) {
- mContext.registerReceiver(receiver, filter);
+ // All of the broadcasts for tests that use this method are sent by the local app, so by
+ // default all receivers can be registered as not exported.
+ registerBroadcastReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED);
+ }
+
+ private void registerBroadcastReceiver(BroadcastReceiver receiver, IntentFilter filter,
+ int flags) {
+ mContext.registerReceiver(receiver, filter, flags);
mRegisteredReceiverList.add(receiver);
}
@@ -1545,6 +1561,105 @@
}.run();
}
+ /**
+ * Verify the receiver should get the broadcast since it has all of the required permissions.
+ */
+ public void testSendBroadcastRequireAllOfPermissions_receiverHasAllPermissions()
+ throws Exception {
+ final ResultReceiver receiver = new ResultReceiver();
+
+ registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION));
+ BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setRequireAllOfPermissions(
+ new String[] { // this test APK has both these permissions
+ android.Manifest.permission.ACCESS_WIFI_STATE,
+ android.Manifest.permission.ACCESS_NETWORK_STATE
+ });
+ mContext.sendBroadcast(new Intent(ResultReceiver.MOCK_ACTION), null,
+ options.toBundle());
+
+ new PollingCheck(BROADCAST_TIMEOUT) {
+ @Override
+ protected boolean check() {
+ return receiver.hasReceivedBroadCast();
+ }
+ }.run();
+ }
+
+ /** The receiver should not get the broadcast if it does not have all the permissions. */
+ public void testSendBroadcastRequireAllOfPermissions_receiverHasSomePermissions()
+ throws Exception {
+ final ResultReceiver receiver = new ResultReceiver();
+
+ registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION));
+ BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setRequireAllOfPermissions(
+ new String[] { // this test APK only has ACCESS_WIFI_STATE
+ android.Manifest.permission.ACCESS_WIFI_STATE,
+ android.Manifest.permission.NETWORK_STACK,
+ });
+
+ mContext.sendBroadcast(
+ new Intent(ResultReceiver.MOCK_ACTION), null,
+ options.toBundle());
+
+ Thread.sleep(BROADCAST_TIMEOUT);
+ assertFalse(receiver.hasReceivedBroadCast());
+ }
+
+ /**
+ * Verify the receiver will get the broadcast since it has none of the excluded permissions.
+ */
+ public void testSendBroadcastRequireNoneOfPermissions_receiverHasNoneOfExcludedPermissions()
+ throws Exception {
+ final ResultReceiver receiver = new ResultReceiver();
+
+ registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION));
+ BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setRequireAllOfPermissions(
+ new String[] { // this test APK has both these permissions
+ android.Manifest.permission.ACCESS_WIFI_STATE,
+ android.Manifest.permission.ACCESS_NETWORK_STATE
+ });
+ options.setRequireNoneOfPermissions(
+ new String[] { // test package does not have NETWORK_STACK
+ android.Manifest.permission.NETWORK_STACK
+ });
+ mContext.sendBroadcast(new Intent(ResultReceiver.MOCK_ACTION), null,
+ options.toBundle());
+
+ new PollingCheck(BROADCAST_TIMEOUT) {
+ @Override
+ protected boolean check() {
+ return receiver.hasReceivedBroadCast();
+ }
+ }.run();
+ }
+
+ /**
+ * Verify the receiver will not get the broadcast since it has one of the excluded permissions.
+ */
+ public void testSendBroadcastRequireNoneOfPermissions_receiverHasExcludedPermissions()
+ throws Exception {
+ final ResultReceiver receiver = new ResultReceiver();
+
+ registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION));
+ BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setRequireAllOfPermissions(
+ new String[] { // this test APK has ACCESS_WIFI_STATE
+ android.Manifest.permission.ACCESS_WIFI_STATE
+ });
+ options.setRequireNoneOfPermissions(
+ new String[] { // test package has ACCESS_NETWORK_STATE
+ android.Manifest.permission.ACCESS_NETWORK_STATE
+ });
+ mContext.sendBroadcast(new Intent(ResultReceiver.MOCK_ACTION), null,
+ options.toBundle());
+
+ Thread.sleep(BROADCAST_TIMEOUT);
+ assertFalse(receiver.hasReceivedBroadCast());
+ }
+
/** The receiver should get the broadcast if it has all the permissions. */
public void testSendBroadcastWithMultiplePermissions_receiverHasAllPermissions()
throws Exception {
@@ -1603,6 +1718,142 @@
assertFalse(receiver.hasReceivedBroadCast());
}
+ /**
+ * Starting from Android 13, a SecurityException is thrown for apps targeting this
+ * release or later that do not specify {@link Context#RECEIVER_EXPORTED} or {@link
+ * Context#RECEIVER_NOT_EXPORTED} when registering for non-system broadcasts.
+ */
+ // TODO(b/206699109): Re-enable test when instrumentation workaround is removed; without a flag
+ // specified the instrumentation workaround automatically adds RECEIVER_EXPORTED.
+ @Suppress
+ public void testRegisterReceiver_noFlags_exceptionThrown() throws Exception {
+ try {
+ final ResultReceiver receiver = new ResultReceiver();
+
+ registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION), 0);
+
+ fail("An app targeting Android 13 and registering a dynamic receiver for a "
+ + "non-system broadcast must receive a SecurityException if "
+ + "RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED is not specified");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ /**
+ * An app targeting Android 13 or later can register for system broadcasts without specifying
+ * {@link Context#RECEIVER_EXPORTED} or {@link Context@RECEIVER_NOT_EXPORTED}.
+ */
+ public void testRegisterReceiver_noFlagsProtectedBroadcast_noExceptionThrown()
+ throws Exception {
+ final ResultReceiver receiver = new ResultReceiver();
+
+ // Intent.ACTION_SCREEN_OFF is a system broadcast and thus should not require a flag
+ // indicating whether the receiver is exported.
+ registerBroadcastReceiver(receiver, new IntentFilter(Intent.ACTION_SCREEN_OFF), 0);
+ }
+
+ /**
+ * An app targeting Android 13 or later can request a sticky broadcast via
+ * {@code Context#registerReceiver} without specifying {@link Context#RECEIVER_EXPORTED} or
+ * {@link Context#RECEIVER_NOT_EXPORTED}.
+ */
+ public void testRegisterReceiver_noFlagsStickyBroadcast_noExceptionThrown() throws Exception {
+ // If a null receiver is specified to Context#registerReceiver, it indicates the caller
+ // is requesting a sticky broadcast without actually registering a receiver; a flag
+ // must not be required in this case.
+ mContext.registerReceiver(null, new IntentFilter(ResultReceiver.MOCK_ACTION), 0);
+ }
+
+ /**
+ * Starting from Android 13, an app targeting this release or later must specify one of either
+ * {@link Context#RECEIVER_EXPORTED} or {@link Context#RECEIVER_NOT_EXPORTED} when registering
+ * a receiver for non-system broadcasts; however if both are specified then an
+ * {@link IllegalArgumentException} should be thrown.
+ */
+ public void testRegisterReceiver_bothFlags_exceptionThrown() throws Exception {
+ try {
+ final ResultReceiver receiver = new ResultReceiver();
+
+ registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION),
+ Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED);
+
+ fail("An app invoke invoking Context#registerReceiver with both RECEIVER_EXPORTED and"
+ + " RECEIVER_NOT_EXPORTED set must receive an IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ /**
+ * Verifies a receiver registered with {@link Context#RECEIVER_EXPORTED} can receive a
+ * broadcast from an external app.
+ *
+ * <p>The broadcast is sent as a shell command since this most closely simulates sending a
+ * broadcast from an external app; sending the broadcast via {@code
+ * ShellIdentityUtils#invokeMethodWithShellPermissionsNoReturn} is still delivered even to
+ * apps that use {@link Context#RECEIVER_NOT_EXPORTED}.
+ */
+ public void testRegisterReceiver_exported_broadcastReceived() throws Exception {
+ final ResultReceiver receiver = new ResultReceiver();
+ registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION),
+ Context.RECEIVER_EXPORTED);
+
+ SystemUtil.runShellCommand(EXTERNAL_APP_BROADCAST_COMMAND);
+
+ new PollingCheck(BROADCAST_TIMEOUT, "The broadcast to the exported receiver"
+ + " was not received within the timeout window") {
+ @Override
+ protected boolean check() {
+ return receiver.hasReceivedBroadCast();
+ }
+ }.run();
+ }
+
+ /**
+ * Verifies a receiver registered with {@link Context#RECEIVER_EXPORTED_UNAUDITED} can receive
+ * a broadcast from an external app.
+ *
+ * <p>{@code Context#RECEIVER_EXPORTED_UNAUDITED} is only intended to be applied to receivers
+ * that have not yet been audited to determine their intended exported state; this test ensures
+ * this flag maintains the existing behavior of exporting the receiver until it can be
+ * evaluated.
+ */
+ public void testRegisterReceiver_exportedUnaudited_broadcastReceived() throws Exception {
+ final ResultReceiver receiver = new ResultReceiver();
+ registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION),
+ Context.RECEIVER_EXPORTED_UNAUDITED);
+
+ SystemUtil.runShellCommand(EXTERNAL_APP_BROADCAST_COMMAND);
+
+ new PollingCheck(BROADCAST_TIMEOUT, "The broadcast to the exported receiver"
+ + " was not received within the timeout window") {
+ @Override
+ protected boolean check() {
+ return receiver.hasReceivedBroadCast();
+ }
+ }.run();
+ }
+
+ /**
+ * Verifies a receiver registered with {@link Context#RECEIVER_NOT_EXPORTED} does not receive
+ * a broadcast from an external app.
+ */
+ // TODO(b/206699109): Re-enable this test once the skip for an external app sending a broadcast
+ // to an unexported receiver is restored in BroadcastQueue.
+ @Suppress
+ public void testRegisterReceiver_notExported_broadcastNotReceived() throws Exception {
+ final ResultReceiver receiver = new ResultReceiver();
+ registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION),
+ Context.RECEIVER_NOT_EXPORTED);
+
+ SystemUtil.runShellCommand(EXTERNAL_APP_BROADCAST_COMMAND);
+
+ Thread.sleep(BROADCAST_TIMEOUT);
+ assertFalse(
+ "An external app must not be able to send a broadcast to a dynamic receiver "
+ + "registered with RECEIVER_NOT_EXPORTED",
+ receiver.hasReceivedBroadCast());
+ }
+
public void testEnforceCallingOrSelfUriPermission() {
try {
Uri uri = Uri.parse("content://ctstest");
diff --git a/tests/tests/content/src/android/content/cts/IntentFilterTest.java b/tests/tests/content/src/android/content/cts/IntentFilterTest.java
index 663cbfa..b5c061d 100644
--- a/tests/tests/content/src/android/content/cts/IntentFilterTest.java
+++ b/tests/tests/content/src/android/content/cts/IntentFilterTest.java
@@ -60,6 +60,7 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
+import java.util.function.Predicate;
public class IntentFilterTest extends AndroidTestCase {
@@ -975,6 +976,14 @@
new String[]{"some.app.domain"},
null);
+ IntentFilter appWithWildcardWebLink = new Match(
+ new String[]{Intent.ACTION_VIEW},
+ new String[]{Intent.CATEGORY_BROWSABLE},
+ null,
+ new String[]{"http", "https"},
+ new String[]{"*.app.domain"},
+ null);
+
IntentFilter browserFilterWithWildcard = new Match(
new String[]{Intent.ACTION_VIEW},
new String[]{Intent.CATEGORY_BROWSABLE},
@@ -1012,6 +1021,13 @@
null,
"https://",
true));
+ checkMatches(appWithWildcardWebLink,
+ new MatchCondition(NO_MATCH_DATA,
+ Intent.ACTION_VIEW,
+ new String[]{Intent.CATEGORY_BROWSABLE},
+ null,
+ "https://",
+ true));
}
public void testWriteToXml() throws IllegalArgumentException, IllegalStateException,
@@ -1774,4 +1790,20 @@
isPrintlnCalled = true;
}
}
-}
\ No newline at end of file
+
+ public void testAsPredicate() throws Exception {
+ final Predicate<Intent> pred = new IntentFilter(ACTION).asPredicate();
+
+ assertTrue(pred.test(new Intent(ACTION)));
+ assertFalse(pred.test(new Intent(CATEGORY)));
+ }
+
+ public void testAsPredicateWithTypeResolution() throws Exception {
+ final ContentResolver resolver = mContext.getContentResolver();
+ final Predicate<Intent> pred = new IntentFilter(ACTION, DATA_STATIC_TYPE)
+ .asPredicateWithTypeResolution(resolver);
+
+ assertTrue(pred.test(new Intent(ACTION).setDataAndType(URI, DATA_STATIC_TYPE)));
+ assertFalse(pred.test(new Intent(ACTION).setDataAndType(URI, DATA_DYNAMIC_TYPE)));
+ }
+}
diff --git a/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java b/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
index 67ec257..45b9005 100644
--- a/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
+++ b/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
@@ -22,11 +22,10 @@
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;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -56,7 +55,7 @@
prefs.edit().clear().commit();
try {
ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(
- mContext.getPackageName(), 0);
+ mContext.getPackageName(), PackageManager.ApplicationInfoFlags.of(0));
mPrefsFile = new File(applicationInfo.dataDir,
"shared_prefs/android.content.cts_preferences.xml");
} catch (PackageManager.NameNotFoundException e) {
diff --git a/tests/tests/content/src/android/content/pm/cts/ApplicationInfoTest.java b/tests/tests/content/src/android/content/pm/cts/ApplicationInfoTest.java
index a77433f..02ee351 100644
--- a/tests/tests/content/src/android/content/pm/cts/ApplicationInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ApplicationInfoTest.java
@@ -38,6 +38,7 @@
import android.content.cts.R;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
@@ -67,6 +68,8 @@
"android.content.cts.directbootunaware";
private static final String PARTIALLY_DIRECT_BOOT_AWARE_PACKAGE_NAME =
"android.content.cts.partiallydirectbootaware";
+ private static final String NO_APPLICATION_PACKAGE_NAME =
+ "android.content.cts.emptytestapp.stub";
private ApplicationInfo mApplicationInfo;
private String mPackageName;
@@ -92,7 +95,8 @@
@Test
public void testWriteToParcel() throws NameNotFoundException {
- mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName, 0);
+ mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName,
+ PackageManager.ApplicationInfoFlags.of(0));
Parcel p = Parcel.obtain();
mApplicationInfo.writeToParcel(p, 0);
@@ -123,7 +127,8 @@
@Test
public void testDescribeContents() throws NameNotFoundException {
- mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName, 0);
+ mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName,
+ PackageManager.ApplicationInfoFlags.of(0));
assertEquals(0, mApplicationInfo.describeContents());
}
@@ -144,7 +149,8 @@
@Test
public void testLoadDescription() throws NameNotFoundException {
- mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName, 0);
+ mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName,
+ PackageManager.ApplicationInfoFlags.of(0));
assertNull(mApplicationInfo.loadDescription(getContext().getPackageManager()));
@@ -155,7 +161,8 @@
@Test
public void verifyOwnInfo() throws NameNotFoundException {
- mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName, 0);
+ mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName,
+ PackageManager.ApplicationInfoFlags.of(0));
assertEquals("Android TestCase", mApplicationInfo.nonLocalizedLabel);
assertEquals(R.drawable.size_48x48, mApplicationInfo.icon);
@@ -169,7 +176,7 @@
public void verifyDefaultValues() throws NameNotFoundException {
// The application "com.android.cts.stub" does not have any attributes set
mApplicationInfo = getContext().getPackageManager().getApplicationInfo(
- SYNC_ACCOUNT_ACCESS_STUB_PACKAGE_NAME, 0);
+ SYNC_ACCOUNT_ACCESS_STUB_PACKAGE_NAME, PackageManager.ApplicationInfoFlags.of(0));
int currentUserId = Process.myUserHandle().getIdentifier();
assertNull(mApplicationInfo.className);
@@ -215,21 +222,29 @@
@Test
public void testDirectBootUnawareAppIsNotEncryptionAware() throws Exception {
ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(
- DIRECT_BOOT_UNAWARE_PACKAGE_NAME, 0);
+ DIRECT_BOOT_UNAWARE_PACKAGE_NAME, PackageManager.ApplicationInfoFlags.of(0));
assertFalse(applicationInfo.isEncryptionAware());
}
@Test
public void testDirectBootUnawareAppCategoryIsAccessibility() throws Exception {
mApplicationInfo = getContext().getPackageManager().getApplicationInfo(
- DIRECT_BOOT_UNAWARE_PACKAGE_NAME, 0);
+ DIRECT_BOOT_UNAWARE_PACKAGE_NAME, PackageManager.ApplicationInfoFlags.of(0));
assertEquals(CATEGORY_ACCESSIBILITY, mApplicationInfo.category);
}
@Test
+ public void testDefaultAppCategoryIsUndefined() throws Exception {
+ final ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(
+ NO_APPLICATION_PACKAGE_NAME, PackageManager.ApplicationInfoFlags.of(0));
+ assertEquals(CATEGORY_UNDEFINED, applicationInfo.category);
+ }
+
+ @Test
public void testPartiallyDirectBootAwareAppIsEncryptionAware() throws Exception {
ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(
- PARTIALLY_DIRECT_BOOT_AWARE_PACKAGE_NAME, 0);
+ PARTIALLY_DIRECT_BOOT_AWARE_PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(0));
assertTrue(applicationInfo.isEncryptionAware());
}
@@ -238,7 +253,8 @@
// Make sure ApplicationInfo.writeToParcel() doesn't do the "squashing",
// because Parcel.allowSquashing() isn't called.
- mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName, 0);
+ mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName,
+ PackageManager.ApplicationInfoFlags.of(0));
final Parcel p = Parcel.obtain();
mApplicationInfo.writeToParcel(p, 0);
@@ -270,7 +286,8 @@
// Make sure ApplicationInfo.writeToParcel() does the "squashing", after
// Parcel.allowSquashing() is called.
- mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName, 0);
+ mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName,
+ PackageManager.ApplicationInfoFlags.of(0));
final Parcel p = Parcel.obtain();
@@ -317,7 +334,7 @@
+ systemPath + vendorPath, packageName);
final PackageInfo info = getContext().getPackageManager().getPackageInfo(
- packageName.trim(), 0 /* flags */);
+ packageName.trim(), PackageManager.PackageInfoFlags.of(0));
assertTrue(packageName + " is not vendor package.", info.applicationInfo.isVendor());
}
@@ -330,7 +347,7 @@
assumeNotNull(packageName);
final PackageInfo info = getContext().getPackageManager().getPackageInfo(
- packageName.trim(), 0 /* flags */);
+ packageName.trim(), PackageManager.PackageInfoFlags.of(0));
assertTrue(packageName + " is not oem package.", info.applicationInfo.isOem());
}
diff --git a/tests/tests/content/src/android/content/pm/cts/ApplicationInfo_DisplayNameComparatorTest.java b/tests/tests/content/src/android/content/pm/cts/ApplicationInfo_DisplayNameComparatorTest.java
index 713c60b..c60fffe 100644
--- a/tests/tests/content/src/android/content/pm/cts/ApplicationInfo_DisplayNameComparatorTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ApplicationInfo_DisplayNameComparatorTest.java
@@ -17,8 +17,8 @@
package android.content.pm.cts;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import android.content.pm.ApplicationInfo.DisplayNameComparator;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.test.AndroidTestCase;
@@ -52,13 +52,15 @@
info2.packageName = PACKAGE_NAME;
assertEquals(0, mDisplayNameComparator.compare(info1, info2));
- info1 = mContext.getPackageManager().getApplicationInfo(PACKAGE_NAME, 0);
+ info1 = mContext.getPackageManager().getApplicationInfo(PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(0));
info2.packageName = PACKAGE_NAME + ".2";
assertTrue((mDisplayNameComparator.compare(info1, info2) < 0));
info1 = new ApplicationInfo();
info1.packageName = PACKAGE_NAME + ".1";
- info2 = mContext.getPackageManager().getApplicationInfo(PACKAGE_NAME, 0);
+ info2 = mContext.getPackageManager().getApplicationInfo(PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(0));
assertTrue((mDisplayNameComparator.compare(info1, info2) > 0));
try {
diff --git a/tests/tests/content/src/android/content/pm/cts/AttributionTest.java b/tests/tests/content/src/android/content/pm/cts/AttributionTest.java
index c1354f7..e7692bb 100644
--- a/tests/tests/content/src/android/content/pm/cts/AttributionTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/AttributionTest.java
@@ -50,7 +50,8 @@
@Test
public void dontGetAttributions() throws Exception {
- PackageInfo packageInfo = sContext.getPackageManager().getPackageInfo(PACKAGE_NAME, 0);
+ PackageInfo packageInfo = sContext.getPackageManager().getPackageInfo(PACKAGE_NAME,
+ PackageManager.PackageInfoFlags.of(0));
assertNotNull(packageInfo);
assertNull(packageInfo.attributions);
}
@@ -58,7 +59,7 @@
@Test
public void getAttributionsAndVerify() throws Exception {
PackageInfo packageInfo = sContext.getPackageManager().getPackageInfo(PACKAGE_NAME,
- PackageManager.GET_ATTRIBUTIONS);
+ PackageManager.PackageInfoFlags.of(PackageManager.GET_ATTRIBUTIONS));
assertNotNull(packageInfo);
assertNotNull(packageInfo.attributions);
assertEquals(packageInfo.attributions.length, NUM_ATTRIBUTIONS);
diff --git a/tests/tests/content/src/android/content/pm/cts/ChecksumsTest.java b/tests/tests/content/src/android/content/pm/cts/ChecksumsTest.java
index fa9f14e..4ca3763 100644
--- a/tests/tests/content/src/android/content/pm/cts/ChecksumsTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ChecksumsTest.java
@@ -36,6 +36,7 @@
import android.app.UiAutomation;
import android.content.ComponentName;
+import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
@@ -51,6 +52,7 @@
import android.content.pm.Signature;
import android.content.pm.cts.util.AbandonAllPackageSessionsRule;
import android.os.Bundle;
+import android.os.Environment;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.AppModeFull;
@@ -153,8 +155,12 @@
return InstrumentationRegistry.getInstrumentation().getUiAutomation();
}
+ private static Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
private static PackageManager getPackageManager() {
- return InstrumentationRegistry.getContext().getPackageManager();
+ return getContext().getPackageManager();
}
private static PackageInstaller getPackageInstaller() {
@@ -457,6 +463,7 @@
+ "5f2888afcb71524196dda0d6dd16a6a3292bb75b431b8ff74fb60d796e882f80");
}
+
@Test
public void testInstallerChecksumsTrustNone() throws Exception {
installApkWithChecksums(TEST_FIXED_APK_DIGESTS);
@@ -602,7 +609,8 @@
// Using the installer's certificate(s).
PackageManager pm = getPackageManager();
- PackageInfo packageInfo = pm.getPackageInfo(CTS_PACKAGE_NAME, GET_SIGNING_CERTIFICATES);
+ PackageInfo packageInfo = pm.getPackageInfo(CTS_PACKAGE_NAME,
+ PackageManager.PackageInfoFlags.of(GET_SIGNING_CERTIFICATES));
final List<Certificate> signatures = convertSignaturesToCertificates(
packageInfo.signingInfo.getApkContentsSigners());
@@ -628,13 +636,106 @@
assertNull(checksums[2].getInstallerCertificate());
}
+ @LargeTest
+ @Test
+ public void testInstallerFileChecksumsDuringInstall() throws Exception {
+ Checksum[] digestsBase = new Checksum[]{new Checksum(TYPE_WHOLE_SHA256, hexStringToBytes(
+ "ed8c7ae1220fe16d558e00cfc37256e6f7088ab90eb04c1bfcb39922a8a5248e")),
+ new Checksum(TYPE_WHOLE_MD5, hexStringToBytes("dd93e23bb8cdab0382fdca0d21a4f1cb"))};
+ Checksum[] digestsSplit0 = new Checksum[]{new Checksum(TYPE_WHOLE_SHA256, hexStringToBytes(
+ "bd9b095a49a9068498b018ce8cb7cc18d411b13a5a5f7fb417d2ff9808ae838e")),
+ new Checksum(TYPE_WHOLE_MD5, hexStringToBytes("f6430e1b795ce2658c49e68d15316b2d"))};
+
+ final Certificate installerCertificate = getInstallerCertificate();
+
+ getUiAutomation().adoptShellPermissionIdentity();
+ PackageInstaller installer = null;
+ int sessionId = -1;
+ try {
+ installer = getPackageInstaller();
+ final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+
+ sessionId = installer.createSession(params);
+ final Session session = installer.openSession(sessionId);
+
+ writeFileToSession(session, "hw5.apk", TEST_V4_APK);
+ session.setChecksums("hw5.apk", Arrays.asList(digestsBase), NO_SIGNATURE);
+
+ writeFileToSession(session, "hw5_split0.apk", TEST_V4_SPLIT0);
+ session.setChecksums("hw5_split0.apk", Arrays.asList(digestsSplit0), NO_SIGNATURE);
+
+ // Workaround to emulate .digests file present in installation.
+ writeChecksumsToSession(session, "hw5.digests", digestsBase);
+ writeChecksumsToSession(session, "hw5_split0.digests", digestsSplit0);
+
+ File dataApp = Environment.getDataAppDirectory(null);
+
+ {
+ LocalListener receiver = new LocalListener();
+
+ session.requestChecksums("hw5.apk", 0, TRUST_ALL, getContext().getMainExecutor(),
+ receiver);
+ ApkChecksum[] checksums = receiver.getResult();
+ assertNotNull(checksums);
+ assertEquals(checksums.length, 3);
+ // base
+ assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+ assertEquals(checksums[0].getSplitName(), null);
+ assertEquals(bytesToHexString(checksums[0].getValue()),
+ "dd93e23bb8cdab0382fdca0d21a4f1cb");
+ assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+ assertEquals(checksums[0].getInstallerCertificate(), installerCertificate);
+ assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+ assertEquals(checksums[1].getSplitName(), null);
+ assertEquals(bytesToHexString(checksums[1].getValue()),
+ "ed8c7ae1220fe16d558e00cfc37256e6f7088ab90eb04c1bfcb39922a8a5248e");
+ assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+ assertEquals(checksums[1].getInstallerCertificate(), installerCertificate);
+ assertEquals(checksums[2].getSplitName(), null);
+ assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+ assertNull(checksums[2].getInstallerPackageName());
+ assertNull(checksums[2].getInstallerCertificate());
+ }
+ {
+ LocalListener receiver = new LocalListener();
+
+ session.requestChecksums("hw5_split0.apk", 0, TRUST_ALL,
+ getContext().getMainExecutor(), receiver);
+ ApkChecksum[] checksums = receiver.getResult();
+ assertNotNull(checksums);
+ assertEquals(checksums.length, 3);
+ // split0
+ assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+ assertEquals(checksums[0].getSplitName(), null);
+ assertEquals(bytesToHexString(checksums[0].getValue()),
+ "f6430e1b795ce2658c49e68d15316b2d");
+ assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+ assertEquals(checksums[0].getInstallerCertificate(), installerCertificate);
+ assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+ assertEquals(checksums[1].getSplitName(), null);
+ assertEquals(bytesToHexString(checksums[1].getValue()),
+ "bd9b095a49a9068498b018ce8cb7cc18d411b13a5a5f7fb417d2ff9808ae838e");
+ assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+ assertEquals(checksums[1].getInstallerCertificate(), installerCertificate);
+ assertEquals(checksums[2].getSplitName(), null);
+ assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+ assertNull(checksums[2].getInstallerPackageName());
+ assertNull(checksums[2].getInstallerCertificate());
+ }
+ } finally {
+ installer.abandonSession(sessionId);
+ getUiAutomation().dropShellPermissionIdentity();
+ }
+ }
+
@Test
public void testInstallerChecksumsTrustWrongInstaller() throws Exception {
installApkWithChecksums(TEST_FIXED_APK_DIGESTS);
// Using certificates from a security app, not the installer (us).
PackageManager pm = getPackageManager();
- PackageInfo packageInfo = pm.getPackageInfo(FIXED_PACKAGE_NAME, GET_SIGNING_CERTIFICATES);
+ PackageInfo packageInfo = pm.getPackageInfo(FIXED_PACKAGE_NAME,
+ PackageManager.PackageInfoFlags.of(GET_SIGNING_CERTIFICATES));
final List<Certificate> signatures = convertSignaturesToCertificates(
packageInfo.signingInfo.getApkContentsSigners());
@@ -1019,7 +1120,8 @@
installPackageIncrementally(TEST_FIXED_APK);
PackageManager pm = getPackageManager();
- PackageInfo packageInfo = pm.getPackageInfo(FIXED_PACKAGE_NAME, 0);
+ PackageInfo packageInfo = pm.getPackageInfo(FIXED_PACKAGE_NAME,
+ PackageManager.PackageInfoFlags.of(0));
final String inPath = packageInfo.applicationInfo.getBaseCodePath();
installApkWithChecksumsIncrementally(inPath);
@@ -1053,7 +1155,8 @@
installPackageIncrementally(TEST_FIXED_APK);
- PackageInfo packageInfo = getPackageManager().getPackageInfo(FIXED_PACKAGE_NAME, 0);
+ PackageInfo packageInfo = getPackageManager().getPackageInfo(FIXED_PACKAGE_NAME,
+ PackageManager.PackageInfoFlags.of(0));
final String inPath = packageInfo.applicationInfo.getBaseCodePath();
final byte[] signature = readSignature();
@@ -1092,7 +1195,8 @@
installPackageIncrementally(TEST_FIXED_APK);
- PackageInfo packageInfo = getPackageManager().getPackageInfo(FIXED_PACKAGE_NAME, 0);
+ PackageInfo packageInfo = getPackageManager().getPackageInfo(FIXED_PACKAGE_NAME,
+ PackageManager.PackageInfoFlags.of(0));
final String inPath = packageInfo.applicationInfo.getBaseCodePath();
installApkWithChecksumsIncrementally(inPath);
@@ -1283,6 +1387,15 @@
}
}
+ private static void writeChecksumsToSession(PackageInstaller.Session session, String name,
+ Checksum[] checksums) throws IOException {
+ try (DataOutputStream dos = new DataOutputStream(session.openWrite(name, 0, -1))) {
+ for (Checksum checksum : checksums) {
+ Checksum.writeToStream(dos, checksum);
+ }
+ }
+ }
+
private String uninstallPackageSilently(String packageName) throws IOException {
return executeShellCommand("pm uninstall " + packageName);
}
@@ -1294,6 +1407,16 @@
.anyMatch(line -> line.substring(prefixLength).equals(packageName));
}
+ private String getAppCodePath(String packageName) throws IOException {
+ final String commandResult = executeShellCommand("pm dump " + packageName);
+ final String prefix = " codePath=";
+ final int prefixLength = prefix.length();
+ return Arrays.stream(commandResult.split("\\r?\\n"))
+ .filter(line -> line.startsWith(prefix))
+ .map(line -> line.substring(prefixLength))
+ .findFirst().get();
+ }
+
@Nonnull
private static String bytesToHexString(byte[] bytes) {
return HexDump.toHexString(bytes, 0, bytes.length, /*upperCase=*/ false);
@@ -1326,7 +1449,7 @@
private Certificate getInstallerCertificate() throws Exception {
PackageManager pm = getPackageManager();
PackageInfo installerPackageInfo = pm.getPackageInfo(CTS_PACKAGE_NAME,
- GET_SIGNING_CERTIFICATES);
+ PackageManager.PackageInfoFlags.of(GET_SIGNING_CERTIFICATES));
final List<Certificate> signatures = convertSignaturesToCertificates(
installerPackageInfo.signingInfo.getApkContentsSigners());
return signatures.get(0);
diff --git a/tests/tests/content/src/android/content/pm/cts/ComponentInfoTest.java b/tests/tests/content/src/android/content/pm/cts/ComponentInfoTest.java
index c937e2a..c74760b 100644
--- a/tests/tests/content/src/android/content/pm/cts/ComponentInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ComponentInfoTest.java
@@ -16,6 +16,7 @@
package android.content.pm.cts;
+import android.content.cts.R;
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
@@ -30,8 +31,6 @@
import com.android.compatibility.common.util.WidgetTestUtils;
-import android.content.cts.R;
-
/**
* Test {@link ComponentInfo}.
@@ -205,7 +204,8 @@
assertEquals("name", mComponentInfo.loadLabel(pm));
mComponentInfo.applicationInfo =
- mContext.getPackageManager().getApplicationInfo(PACKAGE_NAME, 0);
+ mContext.getPackageManager().getApplicationInfo(PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(0));
mComponentInfo.nonLocalizedLabel = null;
mComponentInfo.labelRes = R.string.hello_android;
diff --git a/tests/tests/content/src/android/content/pm/cts/ConfigurationInfoTest.java b/tests/tests/content/src/android/content/pm/cts/ConfigurationInfoTest.java
index f24590b..07c2ca6 100644
--- a/tests/tests/content/src/android/content/pm/cts/ConfigurationInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ConfigurationInfoTest.java
@@ -33,7 +33,7 @@
// Test constructors
new ConfigurationInfo();
PackageInfo pkgInfo = pm.getPackageInfo(getContext().getPackageName(),
- PackageManager.GET_CONFIGURATIONS);
+ PackageManager.PackageInfoFlags.of(PackageManager.GET_CONFIGURATIONS));
ConfigurationInfo[] configInfoArray = pkgInfo.configPreferences;
assertTrue(configInfoArray.length > 0);
ConfigurationInfo configInfo = configInfoArray[0];
diff --git a/tests/tests/content/src/android/content/pm/cts/FeatureGroupInfoTest.java b/tests/tests/content/src/android/content/pm/cts/FeatureGroupInfoTest.java
index da77b0e..92628db 100644
--- a/tests/tests/content/src/android/content/pm/cts/FeatureGroupInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/FeatureGroupInfoTest.java
@@ -56,7 +56,7 @@
};
PackageInfo pi = mPackageManager.getPackageInfo(getContext().getPackageName(),
- PackageManager.GET_CONFIGURATIONS);
+ PackageManager.PackageInfoFlags.of(PackageManager.GET_CONFIGURATIONS));
assertNotNull(pi);
assertNotNull(pi.reqFeatures);
assertNotNull(pi.featureGroups);
diff --git a/tests/tests/content/src/android/content/pm/cts/InstantAppTest.java b/tests/tests/content/src/android/content/pm/cts/InstantAppTest.java
index 2548e86..4bc7a40 100644
--- a/tests/tests/content/src/android/content/pm/cts/InstantAppTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/InstantAppTest.java
@@ -17,8 +17,6 @@
package android.content.pm.cts;
-import android.content.cts.MockActivity;
-
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
@@ -32,7 +30,6 @@
import java.io.File;
import java.util.List;
-import java.util.Set;
/**
* Test instant apps.
@@ -57,7 +54,8 @@
| MATCH_DIRECT_BOOT_UNAWARE
| MATCH_SYSTEM_ONLY;
final List<ResolveInfo> matches =
- mPackageManager.queryIntentServices(resolverIntent, resolveFlags);
+ mPackageManager.queryIntentServices(resolverIntent,
+ PackageManager.ResolveInfoFlags.of(resolveFlags));
assertTrue(matches == null || matches.size() <= 1);
}
@@ -71,7 +69,8 @@
| MATCH_DIRECT_BOOT_UNAWARE
| MATCH_SYSTEM_ONLY;
final List<ResolveInfo> matches =
- mPackageManager.queryIntentActivities(intent, resolveFlags);
+ mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(resolveFlags));
assertTrue(matches == null || matches.size() <= 1);
}
}
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 facc981..fd1b5d2 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
@@ -43,12 +43,16 @@
protected void setUp() throws Exception {
super.setUp();
mPackageManager = getContext().getPackageManager();
- mPackageInfo = mPackageManager.getPackageInfo(PACKAGE_NAME, PackageManager.GET_ACTIVITIES
- | PackageManager.GET_GIDS | PackageManager.GET_CONFIGURATIONS
- | PackageManager.GET_INSTRUMENTATION | PackageManager.GET_PERMISSIONS
- | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS
- | PackageManager.GET_SERVICES | PackageManager.GET_ATTRIBUTIONS
- | PackageManager.GET_SIGNATURES | PackageManager.GET_UNINSTALLED_PACKAGES);
+ mPackageInfo = mPackageManager.getPackageInfo(PACKAGE_NAME,
+ PackageManager.PackageInfoFlags.of(
+ PackageManager.GET_ACTIVITIES | PackageManager.GET_GIDS
+ | PackageManager.GET_CONFIGURATIONS
+ | PackageManager.GET_INSTRUMENTATION
+ | PackageManager.GET_PERMISSIONS
+ | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS
+ | PackageManager.GET_SERVICES | PackageManager.GET_ATTRIBUTIONS
+ | PackageManager.GET_SIGNATURES
+ | PackageManager.GET_UNINSTALLED_PACKAGES));
}
public void testPackageInfoOp() {
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageItemInfoIconTest.java b/tests/tests/content/src/android/content/pm/cts/PackageItemInfoIconTest.java
index c5d5a04..8131b3e 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageItemInfoIconTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageItemInfoIconTest.java
@@ -75,7 +75,8 @@
// size_48x48 is defined as the application icon in this test's AndroidManifest.xml
Drawable expectedIcon = mContext.getDrawable(R.drawable.size_48x48);
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(0));
PackageItemInfo itemInfo = new PackageItemInfo();
itemInfo.icon = 0;
@@ -89,7 +90,8 @@
// start is defined as the Activity icon in this test's AndroidManifest.xml
Drawable expectedIcon = mContext.getDrawable(R.drawable.start);
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(0));
PackageItemInfo itemInfo = getTestItemInfo();
assertEquals(R.drawable.start, itemInfo.icon);
@@ -117,7 +119,8 @@
// size_48x48 is defined as the app icon in this test's AndroidManifest.xml
Drawable expectedIcon = mContext.getDrawable(R.drawable.size_48x48);
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(0));
Drawable icon = appInfo.loadUnbadgedIcon(mPackageManager);
@@ -140,7 +143,8 @@
private PackageItemInfo getTestItemInfo() throws PackageManager.NameNotFoundException {
ComponentName componentName = new ComponentName(PACKAGE_NAME, ACTIVITY_NAME);
- return mPackageManager.getActivityInfo(componentName, 0);
+ return mPackageManager.getActivityInfo(componentName,
+ PackageManager.ComponentInfoFlags.of(0));
}
private boolean comparePixelData(Drawable one, Drawable two) {
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
index 37a8f76..3d4d61b 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
@@ -78,6 +78,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -104,6 +105,16 @@
private static final String TEST_APK_SPLIT2_IDSIG = "HelloWorld5_xhdpi-v4.apk.idsig";
private static final String TEST_APK_MALFORMED = "malformed.apk";
+ private static final String TEST_HW7 = "HelloWorld7.apk";
+ private static final String TEST_HW7_IDSIG = "HelloWorld7.apk.idsig";
+ private static final String TEST_HW7_SPLIT0 = "HelloWorld7_hdpi-v4.apk";
+ private static final String TEST_HW7_SPLIT0_IDSIG = "HelloWorld7_hdpi-v4.apk.idsig";
+ private static final String TEST_HW7_SPLIT1 = "HelloWorld7_mdpi-v4.apk";
+ private static final String TEST_HW7_SPLIT1_IDSIG = "HelloWorld7_mdpi-v4.apk.idsig";
+ private static final String TEST_HW7_SPLIT2 = "HelloWorld7_xhdpi-v4.apk";
+ private static final String TEST_HW7_SPLIT3 = "HelloWorld7_xxhdpi-v4.apk";
+ private static final String TEST_HW7_SPLIT4 = "HelloWorld7_xxxhdpi-v4.apk";
+
private static final long EXPECTED_READ_TIME = 1000L;
private IncrementalInstallSession mSession = null;
@@ -202,6 +213,8 @@
final long blockSize = Os.statvfs("/data/incremental").f_bsize;
final long preAllocatedBlocks = Os.statvfs("/data/incremental").f_bfree;
+ final AtomicLong freeSpaceDifference = new AtomicLong(-1L);
+
mSession =
new IncrementalInstallSession.Builder()
.addApk(Paths.get(apk), Paths.get(idsig))
@@ -216,10 +229,8 @@
try {
final long postAllocatedBlocks =
Os.statvfs("/data/incremental").f_bfree;
- final long freeSpaceDifference =
- (preAllocatedBlocks - postAllocatedBlocks) * blockSize;
- assertTrue(freeSpaceDifference
- >= ((appFileSize * 1.015) + blockSize * 8));
+ freeSpaceDifference.set(
+ (preAllocatedBlocks - postAllocatedBlocks) * blockSize);
} catch (Exception e) {
Log.i(TAG, "ErrnoException: ", e);
throw new AssertionError(e);
@@ -239,6 +250,10 @@
assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ final double freeSpaceExpectedDifference = ((appFileSize * 1.015) + blockSize * 8);
+ assertTrue(freeSpaceDifference.get() + " >= " + freeSpaceExpectedDifference,
+ freeSpaceDifference.get() >= freeSpaceExpectedDifference);
+
String installPath = executeShellCommand(String.format("pm path %s", TEST_APP_PACKAGE))
.replaceFirst("package:", "")
.trim();
@@ -745,12 +760,12 @@
mSession =
new IncrementalInstallSession.Builder()
- .addApk(Paths.get(createApkPath(TEST_APK)),
- Paths.get(createApkPath(TEST_APK_IDSIG)))
- .addApk(Paths.get(createApkPath(TEST_APK_SPLIT0)),
- Paths.get(createApkPath(TEST_APK_SPLIT0_IDSIG)))
- .addApk(Paths.get(createApkPath(TEST_APK_SPLIT1)),
- Paths.get(createApkPath(TEST_APK_SPLIT1_IDSIG)))
+ .addApk(Paths.get(createApkPath(TEST_HW7)),
+ Paths.get(createApkPath(TEST_HW7_IDSIG)))
+ .addApk(Paths.get(createApkPath(TEST_HW7_SPLIT0)),
+ Paths.get(createApkPath(TEST_HW7_SPLIT0_IDSIG)))
+ .addApk(Paths.get(createApkPath(TEST_HW7_SPLIT1)),
+ Paths.get(createApkPath(TEST_HW7_SPLIT1_IDSIG)))
.addExtraArgs("-t", "-i", CTS_PACKAGE_NAME)
.setLogger(new IncrementalDeviceConnection.Logger())
.build();
@@ -778,6 +793,9 @@
final File apkToRead = getSplit("split_config.mdpi.apk");
final long readTime0 = readAndReportTime(apkToRead, 1000);
+ if (readTime0 < EXPECTED_READ_TIME) {
+ executeShellCommand("atrace --async-dump");
+ }
assertTrue(
"Must take longer than " + EXPECTED_READ_TIME + "ms: time0=" + readTime0 + "ms",
readTime0 >= EXPECTED_READ_TIME);
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
index 3d967d7..cbbe421 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
@@ -16,28 +16,60 @@
package android.content.pm.cts;
+import static android.content.Context.RECEIVER_EXPORTED;
+import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256;
+import static android.content.pm.Checksum.TYPE_WHOLE_MERKLE_ROOT_4K_SHA256;
import static android.content.pm.PackageInstaller.DATA_LOADER_TYPE_INCREMENTAL;
import static android.content.pm.PackageInstaller.DATA_LOADER_TYPE_NONE;
import static android.content.pm.PackageInstaller.DATA_LOADER_TYPE_STREAMING;
+import static android.content.pm.PackageInstaller.EXTRA_DATA_LOADER_TYPE;
+import static android.content.pm.PackageInstaller.EXTRA_SESSION_ID;
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
+import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ROOT_HASH;
+import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES;
+import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES;
+import static android.content.pm.PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES;
+import static android.content.pm.PackageManager.VERIFICATION_ALLOW;
+import static android.content.pm.PackageManager.VERIFICATION_REJECT;
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 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.content.pm.ApkChecksum;
+import android.content.pm.ApplicationInfo;
import android.content.pm.DataLoaderParams;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
import android.content.pm.cts.util.AbandonAllPackageSessionsRule;
+import android.os.ConditionVariable;
import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
import android.platform.test.annotations.AppModeFull;
+import android.util.PackageUtils;
import androidx.test.InstrumentationRegistry;
+import com.android.internal.util.ConcurrentUtils;
+import com.android.internal.util.HexDump;
+
+import libcore.util.HexEncoding;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -54,9 +86,15 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.security.cert.CertificateEncodingException;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.Optional;
import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
import java.util.stream.Collectors;
@RunWith(Parameterized.class)
@@ -64,6 +102,8 @@
public class PackageManagerShellCommandTest {
private static final String TEST_APP_PACKAGE = "com.example.helloworld";
+ private static final String CTS_PACKAGE_NAME = "android.content.cts";
+
private static final String TEST_APK_PATH = "/data/local/tmp/cts/content/";
private static final String TEST_HW5 = "HelloWorld5.apk";
private static final String TEST_HW5_SPLIT0 = "HelloWorld5_hdpi-v4.apk";
@@ -78,6 +118,35 @@
private static final String TEST_HW7_SPLIT3 = "HelloWorld7_xxhdpi-v4.apk";
private static final String TEST_HW7_SPLIT4 = "HelloWorld7_xxxhdpi-v4.apk";
+ private static final String TEST_SDK1_PACKAGE = "com.test.sdk1_1";
+ private static final String TEST_SDK1_MAJOR_VERSION2_PACKAGE = "com.test.sdk1_2";
+ private static final String TEST_SDK2_PACKAGE = "com.test.sdk2_2";
+ private static final String TEST_SDK3_PACKAGE = "com.test.sdk3_3";
+ private static final String TEST_SDK_USER_PACKAGE = "com.test.sdk.user";
+
+ private static final String TEST_SDK1_NAME = "com.test.sdk1";
+ private static final String TEST_SDK2_NAME = "com.test.sdk2";
+ private static final String TEST_SDK3_NAME = "com.test.sdk3";
+
+ private static final String TEST_SDK1 = "HelloWorldSdk1.apk";
+ private static final String TEST_SDK1_UPDATED = "HelloWorldSdk1Updated.apk";
+ private static final String TEST_SDK1_MAJOR_VERSION2 = "HelloWorldSdk1MajorVersion2.apk";
+ private static final String TEST_SDK1_DIFFERENT_SIGNER = "HelloWorldSdk1DifferentSigner.apk";
+ private static final String TEST_SDK2 = "HelloWorldSdk2.apk";
+ private static final String TEST_SDK2_UPDATED = "HelloWorldSdk2Updated.apk";
+ private static final String TEST_USING_SDK1 = "HelloWorldUsingSdk1.apk";
+ private static final String TEST_USING_SDK1_AND_SDK2 = "HelloWorldUsingSdk1And2.apk";
+
+ private static final String TEST_SDK3_USING_SDK1 = "HelloWorldSdk3UsingSdk1.apk";
+ private static final String TEST_SDK3_USING_SDK1_AND_SDK2 = "HelloWorldSdk3UsingSdk1And2.apk";
+ private static final String TEST_USING_SDK3 = "HelloWorldUsingSdk3.apk";
+
+ private static final String TEST_HW_NO_APP_STORAGE = "HelloWorldNoAppStorage.apk";
+
+ private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
+
+ private static final long DEFAULT_STREAMING_VERIFICATION_TIMEOUT = 3 * 1000;
+
@Rule
public AbandonAllPackageSessionsRule mAbandonSessionsRule = new AbandonAllPackageSessionsRule();
@@ -94,9 +163,20 @@
private boolean mIncremental = false;
private String mInstall = "";
private String mPackageVerifier = null;
+ private String mUnusedStaticSharedLibsMinCachePeriod = null;
+ private long mStreamingVerificationTimeoutMs = DEFAULT_STREAMING_VERIFICATION_TIMEOUT;
+ private int mSecondUser = -1;
private static PackageInstaller getPackageInstaller() {
- return InstrumentationRegistry.getContext().getPackageManager().getPackageInstaller();
+ return getPackageManager().getPackageInstaller();
+ }
+
+ private static PackageManager getPackageManager() {
+ return InstrumentationRegistry.getContext().getPackageManager();
+ }
+
+ private static Context getContext() {
+ return InstrumentationRegistry.getContext();
}
private static UiAutomation getUiAutomation() {
@@ -170,9 +250,25 @@
uninstallPackageSilently(TEST_APP_PACKAGE);
assertFalse(isAppInstalled(TEST_APP_PACKAGE));
- // Disable the package verifier to avoid the dialog when installing an app.
+ uninstallPackageSilently(TEST_SDK_USER_PACKAGE);
+ uninstallPackageSilently(TEST_SDK3_PACKAGE);
+ uninstallPackageSilently(TEST_SDK2_PACKAGE);
+ uninstallPackageSilently(TEST_SDK1_PACKAGE);
+ uninstallPackageSilently(TEST_SDK1_MAJOR_VERSION2_PACKAGE);
+
mPackageVerifier = executeShellCommand("settings get global verifier_verify_adb_installs");
+ // Disable the package verifier for non-incremental installations to avoid the dialog
+ // when installing an app.
executeShellCommand("settings put global verifier_verify_adb_installs 0");
+
+ mUnusedStaticSharedLibsMinCachePeriod = executeShellCommand(
+ "settings get global unused_static_shared_lib_min_cache_period");
+
+ try {
+ mStreamingVerificationTimeoutMs = Long.parseUnsignedLong(
+ executeShellCommand("settings get global streaming_verifier_timeout"));
+ } catch (NumberFormatException ignore) {
+ }
}
@After
@@ -181,8 +277,26 @@
assertFalse(isAppInstalled(TEST_APP_PACKAGE));
assertEquals(null, getSplits(TEST_APP_PACKAGE));
- // Reset the package verifier setting to its original value.
+ uninstallPackageSilently(TEST_SDK_USER_PACKAGE);
+ uninstallPackageSilently(TEST_SDK3_PACKAGE);
+ uninstallPackageSilently(TEST_SDK2_PACKAGE);
+ uninstallPackageSilently(TEST_SDK1_PACKAGE);
+ uninstallPackageSilently(TEST_SDK1_MAJOR_VERSION2_PACKAGE);
+
+ // Reset the global settings to their original values.
executeShellCommand("settings put global verifier_verify_adb_installs " + mPackageVerifier);
+ executeShellCommand("settings put global unused_static_shared_lib_min_cache_period "
+ + mUnusedStaticSharedLibsMinCachePeriod);
+
+ // Set the test override to invalid.
+ setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest", "invalid");
+ setSystemProperty("debug.pm.prune_unused_shared_libraries_delay", "invalid");
+ setSystemProperty("debug.pm.adb_verifier_override_package", "invalid");
+
+ if (mSecondUser != -1) {
+ stopUser(mSecondUser);
+ removeUser(mSecondUser);
+ }
}
private boolean checkIncrementalDeliveryFeature() {
@@ -575,6 +689,835 @@
}
}
+ @Test
+ public void testSdkInstallAndUpdate() throws Exception {
+ installPackage(TEST_SDK1);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+ // Same APK.
+ installPackage(TEST_SDK1);
+
+ // Updated APK.
+ installPackage(TEST_SDK1_UPDATED);
+
+ // Reverted APK.
+ installPackage(TEST_SDK1);
+
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+ }
+
+ @Test
+ public void testSdkInstallMultipleMajorVersions() throws Exception {
+ // Major version 1.
+ installPackage(TEST_SDK1);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+ // Major version 2.
+ installPackage(TEST_SDK1_MAJOR_VERSION2);
+
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 2));
+ }
+
+ @Test
+ public void testSdkInstallMultipleMinorVersionsWrongSignature() throws Exception {
+ // Major version 1.
+ installPackage(TEST_SDK1);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+ // Major version 1, different signer.
+ installPackage(TEST_SDK1_DIFFERENT_SIGNER,
+ "Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Package com.test.sdk1_1 signatures "
+ + "do not match previously installed version");
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+ }
+
+ @Test
+ public void testSdkInstallMultipleMajorVersionsWrongSignature() throws Exception {
+ // Major version 1.
+ installPackage(TEST_SDK1_DIFFERENT_SIGNER);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+ // Major version 2.
+ installPackage(TEST_SDK1_MAJOR_VERSION2,
+ "Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Package com.test.sdk1_1 signatures "
+ + "do not match previously installed version");
+
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+ }
+
+ @Test
+ public void testSdkInstallAndUpdateTwoMajorVersions() throws Exception {
+ installPackage(TEST_SDK1);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+ installPackage(TEST_SDK2);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+ assertTrue(isSdkInstalled(TEST_SDK2_NAME, 2));
+
+ // Same APK.
+ installPackage(TEST_SDK1);
+ installPackage(TEST_SDK2);
+
+ // Updated APK.
+ installPackage(TEST_SDK1_UPDATED);
+ installPackage(TEST_SDK2_UPDATED);
+
+ // Reverted APK.
+ installPackage(TEST_SDK1);
+ installPackage(TEST_SDK2);
+
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+ assertTrue(isSdkInstalled(TEST_SDK2_NAME, 2));
+ }
+
+ @Test
+ public void testAppUsingSdkInstallAndUpdate() throws Exception {
+ // Try to install without required SDK1.
+ installPackage(TEST_USING_SDK1, "Failure [INSTALL_FAILED_MISSING_SHARED_LIBRARY");
+ assertFalse(isAppInstalled(TEST_SDK_USER_PACKAGE));
+
+ // Now install the required SDK1.
+ installPackage(TEST_SDK1);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+ setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest",
+ getPackageCertDigest(TEST_SDK1_PACKAGE));
+
+ // Install and uninstall.
+ installPackage(TEST_USING_SDK1);
+ uninstallPackageSilently(TEST_SDK_USER_PACKAGE);
+
+ // Update SDK1.
+ installPackage(TEST_SDK1_UPDATED);
+
+ // Install again.
+ installPackage(TEST_USING_SDK1);
+
+ // Check resolution API.
+ getUiAutomation().adoptShellPermissionIdentity();
+ try {
+ ApplicationInfo appInfo = getPackageManager().getApplicationInfo(TEST_SDK_USER_PACKAGE,
+ PackageManager.ApplicationInfoFlags.of(GET_SHARED_LIBRARY_FILES));
+ assertEquals(1, appInfo.sharedLibraryInfos.size());
+ SharedLibraryInfo libInfo = appInfo.sharedLibraryInfos.get(0);
+ assertEquals("com.test.sdk1", libInfo.getName());
+ assertEquals(1, libInfo.getLongVersion());
+ } finally {
+ getUiAutomation().dropShellPermissionIdentity();
+ }
+
+ // Try to install without required SDK2.
+ installPackage(TEST_USING_SDK1_AND_SDK2, "Failure [INSTALL_FAILED_MISSING_SHARED_LIBRARY");
+
+ // Now install the required SDK2.
+ installPackage(TEST_SDK2);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+ assertTrue(isSdkInstalled(TEST_SDK2_NAME, 2));
+
+ // Install and uninstall.
+ installPackage(TEST_USING_SDK1_AND_SDK2);
+ uninstallPackageSilently(TEST_SDK_USER_PACKAGE);
+
+ // Update both SDKs.
+ installPackage(TEST_SDK1_UPDATED);
+ installPackage(TEST_SDK2_UPDATED);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+ assertTrue(isSdkInstalled(TEST_SDK2_NAME, 2));
+
+ // Install again.
+ installPackage(TEST_USING_SDK1_AND_SDK2);
+
+ // Check resolution API.
+ getUiAutomation().adoptShellPermissionIdentity();
+ try {
+ ApplicationInfo appInfo = getPackageManager().getApplicationInfo(TEST_SDK_USER_PACKAGE,
+ PackageManager.ApplicationInfoFlags.of(GET_SHARED_LIBRARY_FILES));
+ assertEquals(2, appInfo.sharedLibraryInfos.size());
+ assertEquals("com.test.sdk1", appInfo.sharedLibraryInfos.get(0).getName());
+ assertEquals(1, appInfo.sharedLibraryInfos.get(0).getLongVersion());
+ assertEquals("com.test.sdk2", appInfo.sharedLibraryInfos.get(1).getName());
+ assertEquals(2, appInfo.sharedLibraryInfos.get(1).getLongVersion());
+ } finally {
+ getUiAutomation().dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
+ public void testInstallFailsMismatchingCertificate() throws Exception {
+ // Install the required SDK1.
+ installPackage(TEST_SDK1);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+ // Try to install the package with empty digest.
+ installPackage(TEST_USING_SDK1, "Failure [INSTALL_FAILED_MISSING_SHARED_LIBRARY");
+ }
+
+ @Test
+ public void testUninstallSdkWhileAppUsing() throws Exception {
+ // Install the required SDK1.
+ installPackage(TEST_SDK1);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+ setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest",
+ getPackageCertDigest(TEST_SDK1_PACKAGE));
+
+ // Install the package.
+ installPackage(TEST_USING_SDK1);
+
+ uninstallPackage(TEST_SDK1_PACKAGE, "Failure [DELETE_FAILED_USED_SHARED_LIBRARY]");
+ }
+
+ @Test
+ public void testGetSharedLibraries() throws Exception {
+ // Install the SDK1.
+ installPackage(TEST_SDK1);
+ {
+ List<SharedLibraryInfo> libs = getSharedLibraries();
+ SharedLibraryInfo sdk1 = findLibrary(libs, "com.test.sdk1", 1);
+ assertNotNull(sdk1);
+ SharedLibraryInfo sdk2 = findLibrary(libs, "com.test.sdk2", 2);
+ assertNull(sdk2);
+ }
+
+ // Install the SDK2.
+ installPackage(TEST_SDK2);
+ {
+ List<SharedLibraryInfo> libs = getSharedLibraries();
+ SharedLibraryInfo sdk1 = findLibrary(libs, "com.test.sdk1", 1);
+ assertNotNull(sdk1);
+ SharedLibraryInfo sdk2 = findLibrary(libs, "com.test.sdk2", 2);
+ assertNotNull(sdk2);
+ }
+
+ // Install and uninstall the user package.
+ {
+ setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest",
+ getPackageCertDigest(TEST_SDK1_PACKAGE));
+
+ installPackage(TEST_USING_SDK1_AND_SDK2);
+
+ List<SharedLibraryInfo> libs = getSharedLibraries();
+ SharedLibraryInfo sdk1 = findLibrary(libs, "com.test.sdk1", 1);
+ assertNotNull(sdk1);
+ SharedLibraryInfo sdk2 = findLibrary(libs, "com.test.sdk2", 2);
+ assertNotNull(sdk2);
+
+ assertEquals(TEST_SDK_USER_PACKAGE,
+ sdk1.getDependentPackages().get(0).getPackageName());
+ assertEquals(TEST_SDK_USER_PACKAGE,
+ sdk2.getDependentPackages().get(0).getPackageName());
+
+ uninstallPackageSilently(TEST_SDK_USER_PACKAGE);
+ }
+
+ // Uninstall the SDK1.
+ uninstallPackageSilently(TEST_SDK1_PACKAGE);
+ {
+ List<SharedLibraryInfo> libs = getSharedLibraries();
+ SharedLibraryInfo sdk1 = findLibrary(libs, "com.test.sdk1", 1);
+ assertNull(sdk1);
+ SharedLibraryInfo sdk2 = findLibrary(libs, "com.test.sdk2", 2);
+ assertNotNull(sdk2);
+ }
+
+ // Uninstall the SDK2.
+ uninstallPackageSilently(TEST_SDK2_PACKAGE);
+ {
+ List<SharedLibraryInfo> libs = getSharedLibraries();
+ SharedLibraryInfo sdk1 = findLibrary(libs, "com.test.sdk1", 1);
+ assertNull(sdk1);
+ SharedLibraryInfo sdk2 = findLibrary(libs, "com.test.sdk2", 2);
+ assertNull(sdk2);
+ }
+ }
+
+ @Test
+ public void testUninstallUnusedSdks() throws Exception {
+ installPackage(TEST_SDK1);
+ installPackage(TEST_SDK2);
+
+ setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest",
+ getPackageCertDigest(TEST_SDK1_PACKAGE));
+ installPackage(TEST_USING_SDK1_AND_SDK2);
+
+ setSystemProperty("debug.pm.prune_unused_shared_libraries_delay", "0");
+ executeShellCommand("settings put global unused_static_shared_lib_min_cache_period 0");
+ uninstallPackageSilently(TEST_SDK_USER_PACKAGE);
+
+ // Wait for 3secs max.
+ for (int i = 0; i < 30; ++i) {
+ if (!isSdkInstalled(TEST_SDK1_NAME, 1) && !isSdkInstalled(TEST_SDK2_NAME, 2)) {
+ break;
+ }
+ final int beforeRetryDelayMs = 100;
+ Thread.currentThread().sleep(beforeRetryDelayMs);
+ }
+ assertFalse(isSdkInstalled(TEST_SDK1_NAME, 1));
+ assertFalse(isSdkInstalled(TEST_SDK2_NAME, 2));
+ }
+
+ @Test
+ public void testAppUsingSdkUsingSdkInstallAndUpdate() throws Exception {
+ // Try to install without required SDK1.
+ installPackage(TEST_USING_SDK3, "Failure [INSTALL_FAILED_MISSING_SHARED_LIBRARY");
+ assertFalse(isAppInstalled(TEST_SDK_USER_PACKAGE));
+
+ // Try to install SDK3 without required SDK1.
+ installPackage(TEST_SDK3_USING_SDK1, "Failure [INSTALL_FAILED_MISSING_SHARED_LIBRARY");
+ assertFalse(isSdkInstalled(TEST_SDK3_NAME, 3));
+
+ // Now install the required SDK1.
+ installPackage(TEST_SDK1);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+ setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest",
+ getPackageCertDigest(TEST_SDK1_PACKAGE));
+
+ // Now install the required SDK3.
+ installPackage(TEST_SDK3_USING_SDK1);
+ assertTrue(isSdkInstalled(TEST_SDK3_NAME, 3));
+
+ // Install and uninstall.
+ installPackage(TEST_USING_SDK3);
+ uninstallPackageSilently(TEST_SDK_USER_PACKAGE);
+
+ // Update SDK1.
+ installPackage(TEST_SDK1_UPDATED);
+
+ // Install again.
+ installPackage(TEST_USING_SDK3);
+
+ // Check resolution API.
+ getUiAutomation().adoptShellPermissionIdentity();
+ try {
+ ApplicationInfo appInfo = getPackageManager().getApplicationInfo(TEST_SDK_USER_PACKAGE,
+ PackageManager.ApplicationInfoFlags.of(GET_SHARED_LIBRARY_FILES));
+ assertEquals(1, appInfo.sharedLibraryInfos.size());
+ SharedLibraryInfo libInfo = appInfo.sharedLibraryInfos.get(0);
+ assertEquals("com.test.sdk3", libInfo.getName());
+ assertEquals(3, libInfo.getLongVersion());
+ } finally {
+ getUiAutomation().dropShellPermissionIdentity();
+ }
+
+ // Try to install updated SDK3 without required SDK2.
+ installPackage(TEST_SDK3_USING_SDK1_AND_SDK2,
+ "Failure [INSTALL_FAILED_MISSING_SHARED_LIBRARY");
+
+ // Now install the required SDK2.
+ installPackage(TEST_SDK2);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+ assertTrue(isSdkInstalled(TEST_SDK2_NAME, 2));
+
+ installPackage(TEST_SDK3_USING_SDK1_AND_SDK2);
+ assertTrue(isSdkInstalled(TEST_SDK3_NAME, 3));
+
+ // Install and uninstall.
+ installPackage(TEST_USING_SDK3);
+ uninstallPackageSilently(TEST_SDK_USER_PACKAGE);
+
+ // Update both SDKs.
+ installPackage(TEST_SDK1_UPDATED);
+ installPackage(TEST_SDK2_UPDATED);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+ assertTrue(isSdkInstalled(TEST_SDK2_NAME, 2));
+
+ // Install again.
+ installPackage(TEST_USING_SDK3);
+
+ // Check resolution API.
+ getUiAutomation().adoptShellPermissionIdentity();
+ try {
+ ApplicationInfo appInfo = getPackageManager().getApplicationInfo(TEST_SDK_USER_PACKAGE,
+ PackageManager.ApplicationInfoFlags.of(GET_SHARED_LIBRARY_FILES));
+ assertEquals(1, appInfo.sharedLibraryInfos.size());
+ assertEquals("com.test.sdk3", appInfo.sharedLibraryInfos.get(0).getName());
+ assertEquals(3, appInfo.sharedLibraryInfos.get(0).getLongVersion());
+ } finally {
+ getUiAutomation().dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
+ public void testSdkUsingSdkInstallAndUpdate() throws Exception {
+ // Try to install without required SDK1.
+ installPackage(TEST_SDK3_USING_SDK1, "Failure [INSTALL_FAILED_MISSING_SHARED_LIBRARY");
+ assertFalse(isSdkInstalled(TEST_SDK3_NAME, 3));
+
+ // Now install the required SDK1.
+ installPackage(TEST_SDK1);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+ setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest",
+ getPackageCertDigest(TEST_SDK1_PACKAGE));
+
+ // Install and uninstall.
+ installPackage(TEST_SDK3_USING_SDK1);
+ uninstallPackageSilently(TEST_SDK3_PACKAGE);
+
+ // Update SDK1.
+ installPackage(TEST_SDK1_UPDATED);
+
+ // Install again.
+ installPackage(TEST_SDK3_USING_SDK1);
+
+ // Check resolution API.
+ {
+ List<SharedLibraryInfo> libs = getSharedLibraries();
+ SharedLibraryInfo sdk3 = findLibrary(libs, "com.test.sdk3", 3);
+ assertNotNull(sdk3);
+ List<SharedLibraryInfo> deps = sdk3.getDependencies();
+ assertEquals(1, deps.size());
+ SharedLibraryInfo libInfo = deps.get(0);
+ assertEquals("com.test.sdk1", libInfo.getName());
+ assertEquals(1, libInfo.getLongVersion());
+ }
+
+ // Try to install without required SDK2.
+ installPackage(TEST_SDK3_USING_SDK1_AND_SDK2,
+ "Failure [INSTALL_FAILED_MISSING_SHARED_LIBRARY");
+
+ // Now install the required SDK2.
+ installPackage(TEST_SDK2);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+ assertTrue(isSdkInstalled(TEST_SDK2_NAME, 2));
+
+ // Install and uninstall.
+ installPackage(TEST_SDK3_USING_SDK1_AND_SDK2);
+ uninstallPackageSilently(TEST_SDK3_PACKAGE);
+
+ // Update both SDKs.
+ installPackage(TEST_SDK1_UPDATED);
+ installPackage(TEST_SDK2_UPDATED);
+ assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+ assertTrue(isSdkInstalled(TEST_SDK2_NAME, 2));
+
+ // Install again.
+ installPackage(TEST_SDK3_USING_SDK1_AND_SDK2);
+
+ // Check resolution API.
+ {
+ List<SharedLibraryInfo> libs = getSharedLibraries();
+ SharedLibraryInfo sdk3 = findLibrary(libs, "com.test.sdk3", 3);
+ assertNotNull(sdk3);
+ List<SharedLibraryInfo> deps = sdk3.getDependencies();
+ assertEquals(2, deps.size());
+ assertEquals("com.test.sdk1", deps.get(0).getName());
+ assertEquals(1, deps.get(0).getLongVersion());
+ assertEquals("com.test.sdk2", deps.get(1).getName());
+ assertEquals(2, deps.get(1).getLongVersion());
+ }
+ }
+
+ private void runPackageVerifierTest(BiConsumer<Context, Intent> onBroadcast)
+ throws Exception {
+ runPackageVerifierTest("Success", onBroadcast);
+ }
+
+ private void runPackageVerifierTest(String expectedResultStartsWith,
+ BiConsumer<Context, Intent> onBroadcast) throws Exception {
+ AtomicReference<Thread> onBroadcastThread = new AtomicReference<>();
+
+ runPackageVerifierTestSync(expectedResultStartsWith, (context, intent) -> {
+ Thread thread = new Thread(() -> onBroadcast.accept(context, intent));
+ thread.start();
+ onBroadcastThread.set(thread);
+ });
+
+ onBroadcastThread.get().join();
+ }
+
+ private void runPackageVerifierTestSync(String expectedResultStartsWith,
+ BiConsumer<Context, Intent> onBroadcast) throws Exception {
+ // Install a package.
+ installPackage(TEST_HW5);
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+
+ getUiAutomation().adoptShellPermissionIdentity(
+ android.Manifest.permission.PACKAGE_VERIFICATION_AGENT);
+
+ // Create a single-use broadcast receiver
+ BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ context.unregisterReceiver(this);
+ onBroadcast.accept(context, intent);
+ }
+ };
+ // Create an intent-filter and register the receiver
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
+ intentFilter.addDataType(PACKAGE_MIME_TYPE);
+ getContext().registerReceiver(broadcastReceiver, intentFilter, RECEIVER_EXPORTED);
+
+ // Enable verification.
+ executeShellCommand("settings put global verifier_verify_adb_installs 1");
+ // Override verifier for updates of debuggable apps.
+ setSystemProperty("debug.pm.adb_verifier_override_package", CTS_PACKAGE_NAME);
+
+ // Update the package, should trigger verifier override.
+ installPackage(TEST_HW7, expectedResultStartsWith);
+ }
+
+ @Test
+ public void testPackageVerifierAllow() throws Exception {
+ AtomicInteger dataLoaderType = new AtomicInteger(-1);
+
+ runPackageVerifierTest((context, intent) -> {
+ int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
+ assertNotEquals(-1, verificationId);
+
+ dataLoaderType.set(intent.getIntExtra(EXTRA_DATA_LOADER_TYPE, -1));
+ int sessionId = intent.getIntExtra(EXTRA_SESSION_ID, -1);
+ assertNotEquals(-1, sessionId);
+
+ getPackageManager().verifyPendingInstall(verificationId, VERIFICATION_ALLOW);
+ });
+
+ assertEquals(mDataLoaderType, dataLoaderType.get());
+ }
+
+ @Test
+ public void testPackageVerifierReject() throws Exception {
+ AtomicInteger dataLoaderType = new AtomicInteger(-1);
+
+ runPackageVerifierTest("Failure [INSTALL_FAILED_VERIFICATION_FAILURE: Install not allowed]",
+ (context, intent) -> {
+ int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
+ assertNotEquals(-1, verificationId);
+
+ dataLoaderType.set(intent.getIntExtra(EXTRA_DATA_LOADER_TYPE, -1));
+ int sessionId = intent.getIntExtra(EXTRA_SESSION_ID, -1);
+ assertNotEquals(-1, sessionId);
+
+ getPackageManager().verifyPendingInstall(verificationId, VERIFICATION_REJECT);
+ });
+
+ assertEquals(mDataLoaderType, dataLoaderType.get());
+ }
+
+ @Test
+ public void testPackageVerifierRejectAfterTimeout() throws Exception {
+ AtomicInteger dataLoaderType = new AtomicInteger(-1);
+
+ runPackageVerifierTestSync("Success", (context, intent) -> {
+ int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
+ assertNotEquals(-1, verificationId);
+
+ dataLoaderType.set(intent.getIntExtra(EXTRA_DATA_LOADER_TYPE, -1));
+ int sessionId = intent.getIntExtra(EXTRA_SESSION_ID, -1);
+ assertNotEquals(-1, sessionId);
+
+ try {
+ if (mDataLoaderType == DATA_LOADER_TYPE_INCREMENTAL) {
+ // For streaming installations, the timeout is fixed at 3secs and always
+ // allow the install. Try to extend the timeout and then reject after
+ // much shorter time.
+ getPackageManager().extendVerificationTimeout(verificationId,
+ VERIFICATION_REJECT, mStreamingVerificationTimeoutMs * 3);
+ Thread.sleep(mStreamingVerificationTimeoutMs * 2);
+ getPackageManager().verifyPendingInstall(verificationId,
+ VERIFICATION_REJECT);
+ } else {
+ getPackageManager().verifyPendingInstall(verificationId,
+ VERIFICATION_ALLOW);
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ assertEquals(mDataLoaderType, dataLoaderType.get());
+ }
+
+ @Test
+ public void testPackageVerifierWithExtensionAndTimeout() throws Exception {
+ AtomicInteger dataLoaderType = new AtomicInteger(-1);
+
+ runPackageVerifierTest((context, intent) -> {
+ int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
+ assertNotEquals(-1, verificationId);
+
+ dataLoaderType.set(intent.getIntExtra(EXTRA_DATA_LOADER_TYPE, -1));
+ int sessionId = intent.getIntExtra(EXTRA_SESSION_ID, -1);
+ assertNotEquals(-1, sessionId);
+
+ try {
+ if (mDataLoaderType == DATA_LOADER_TYPE_INCREMENTAL) {
+ // For streaming installations, the timeout is fixed at 3secs and always
+ // allow the install. Try to extend the timeout and then reject after
+ // much shorter time.
+ getPackageManager().extendVerificationTimeout(verificationId,
+ VERIFICATION_REJECT, mStreamingVerificationTimeoutMs * 3);
+ Thread.sleep(mStreamingVerificationTimeoutMs * 2);
+ getPackageManager().verifyPendingInstall(verificationId,
+ VERIFICATION_REJECT);
+ } else {
+ getPackageManager().verifyPendingInstall(verificationId,
+ VERIFICATION_ALLOW);
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ assertEquals(mDataLoaderType, dataLoaderType.get());
+ }
+
+ @Test
+ public void testPackageVerifierWithChecksums() throws Exception {
+ AtomicInteger dataLoaderType = new AtomicInteger(-1);
+ List<ApkChecksum> checksums = new ArrayList<>();
+ StringBuilder rootHash = new StringBuilder();
+
+ runPackageVerifierTest((context, intent) -> {
+ int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
+ assertNotEquals(-1, verificationId);
+
+ dataLoaderType.set(intent.getIntExtra(EXTRA_DATA_LOADER_TYPE, -1));
+ int sessionId = intent.getIntExtra(EXTRA_SESSION_ID, -1);
+ assertNotEquals(-1, sessionId);
+
+ try {
+ PackageInstaller.Session session = getPackageInstaller().openSession(sessionId);
+ assertNotNull(session);
+
+ rootHash.append(intent.getStringExtra(EXTRA_VERIFICATION_ROOT_HASH));
+
+ String[] names = session.getNames();
+ assertEquals(1, names.length);
+ session.requestChecksums(names[0], 0, PackageManager.TRUST_ALL,
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ result -> checksums.addAll(result));
+ } catch (IOException | CertificateEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ assertEquals(mDataLoaderType, dataLoaderType.get());
+
+ assertEquals(1, checksums.size());
+
+ if (mDataLoaderType == DATA_LOADER_TYPE_INCREMENTAL) {
+ assertEquals(TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, checksums.get(0).getType());
+ assertEquals(rootHash.toString(),
+ "base.apk:" + HexDump.toHexString(checksums.get(0).getValue()));
+ } else {
+ assertEquals(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256, checksums.get(0).getType());
+ }
+ }
+
+ @Test
+ public void testGetFirstInstallTime() throws Exception {
+ final int currentUser = getContext().getUserId();
+ final long startTimeMillisForCurrentUser = System.currentTimeMillis();
+ installPackage(TEST_HW5);
+ assertTrue(isAppInstalledForUser(TEST_APP_PACKAGE, currentUser));
+ final long origFirstInstallTimeForCurrentUser = getFirstInstallTimeAsUser(
+ TEST_APP_PACKAGE, currentUser);
+ // Validate the timestamp
+ assertTrue(origFirstInstallTimeForCurrentUser > 0);
+ assertTrue(startTimeMillisForCurrentUser < origFirstInstallTimeForCurrentUser);
+ assertTrue(System.currentTimeMillis() > origFirstInstallTimeForCurrentUser);
+
+ // Install again with replace and the firstInstallTime should remain the same
+ installPackage(TEST_HW5);
+ long firstInstallTimeForCurrentUser = getFirstInstallTimeAsUser(
+ TEST_APP_PACKAGE, currentUser);
+ assertEquals(origFirstInstallTimeForCurrentUser, firstInstallTimeForCurrentUser);
+
+ // Start another user and install this test itself for that user
+ mSecondUser = createUser("Another User");
+ assertTrue(startUser(mSecondUser));
+ long startTimeMillisForSecondUser = System.currentTimeMillis();
+ installExistingPackageAsUser(getContext().getPackageName(), mSecondUser);
+ assertTrue(isAppInstalledForUser(getContext().getPackageName(), mSecondUser));
+ // Install test package with replace
+ installPackageAsUser(TEST_HW5, mSecondUser);
+ assertTrue(isAppInstalledForUser(TEST_APP_PACKAGE, mSecondUser));
+ firstInstallTimeForCurrentUser = getFirstInstallTimeAsUser(
+ TEST_APP_PACKAGE, currentUser);
+ // firstInstallTime should remain unchanged for the current user
+ assertEquals(origFirstInstallTimeForCurrentUser, firstInstallTimeForCurrentUser);
+
+ long firstInstallTimeForSecondUser = getFirstInstallTimeAsUser(
+ TEST_APP_PACKAGE, mSecondUser);
+ // firstInstallTime for the other user should be different
+ assertNotEquals(firstInstallTimeForCurrentUser, firstInstallTimeForSecondUser);
+ assertTrue(startTimeMillisForSecondUser < firstInstallTimeForSecondUser);
+ assertTrue(System.currentTimeMillis() > firstInstallTimeForSecondUser);
+
+ // Uninstall for the other user
+ uninstallPackageAsUser(TEST_APP_PACKAGE, mSecondUser);
+ assertFalse(isAppInstalledForUser(TEST_APP_PACKAGE, mSecondUser));
+ // Install test package as an existing package
+ startTimeMillisForSecondUser = System.currentTimeMillis();
+ installExistingPackageAsUser(TEST_APP_PACKAGE, mSecondUser);
+ assertTrue(isAppInstalledForUser(TEST_APP_PACKAGE, mSecondUser));
+
+ firstInstallTimeForCurrentUser = getFirstInstallTimeAsUser(
+ TEST_APP_PACKAGE, currentUser);
+ // firstInstallTime still remains unchanged for the current user
+ assertEquals(origFirstInstallTimeForCurrentUser, firstInstallTimeForCurrentUser);
+ firstInstallTimeForSecondUser = getFirstInstallTimeAsUser(TEST_APP_PACKAGE, mSecondUser);
+ // firstInstallTime for the other user should be different
+ assertNotEquals(firstInstallTimeForCurrentUser, firstInstallTimeForSecondUser);
+ assertTrue(startTimeMillisForSecondUser < firstInstallTimeForSecondUser);
+ assertTrue(System.currentTimeMillis() > firstInstallTimeForSecondUser);
+
+ // Uninstall for all users
+ uninstallPackageSilently(TEST_APP_PACKAGE);
+ assertFalse(isAppInstalledForUser(TEST_APP_PACKAGE, currentUser));
+ assertFalse(isAppInstalledForUser(TEST_APP_PACKAGE, mSecondUser));
+ // Reinstall for all users
+ installPackage(TEST_HW5);
+ assertTrue(isAppInstalledForUser(TEST_APP_PACKAGE, currentUser));
+ assertTrue(isAppInstalledForUser(TEST_APP_PACKAGE, mSecondUser));
+ firstInstallTimeForCurrentUser = getFirstInstallTimeAsUser(TEST_APP_PACKAGE, currentUser);
+ // First install time is now different because the package was fully uninstalled
+ assertNotEquals(origFirstInstallTimeForCurrentUser, firstInstallTimeForCurrentUser);
+ firstInstallTimeForSecondUser = getFirstInstallTimeAsUser(TEST_APP_PACKAGE, mSecondUser);
+ // Same firstInstallTime because package was installed for both users at the same time
+ assertEquals(firstInstallTimeForCurrentUser, firstInstallTimeForSecondUser);
+ }
+
+ private long getFirstInstallTimeAsUser(String packageName, int userId)
+ throws PackageManager.NameNotFoundException {
+ final Context contextAsUser = getContext().createContextAsUser(UserHandle.of(userId), 0);
+ final PackageManager packageManager = contextAsUser.getPackageManager();
+ final PackageInfo packageInfo = packageManager.getPackageInfo(packageName,
+ PackageManager.PackageInfoFlags.of(0));
+ return packageInfo.firstInstallTime;
+ }
+
+ @Test
+ public void testAppWithNoAppStorageUpdateSuccess() throws Exception {
+ installPackage(TEST_HW_NO_APP_STORAGE);
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ // Updates that don't change value of NO_APP_DATA_STORAGE property are allowed.
+ installPackage(TEST_HW_NO_APP_STORAGE);
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testAppUpdateAddsNoAppDataStorageProperty() throws Exception {
+ installPackage(TEST_HW5);
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ installPackage(
+ TEST_HW_NO_APP_STORAGE,
+ "Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Update "
+ + "attempted to change value of "
+ + "android.internal.PROPERTY_NO_APP_DATA_STORAGE");
+ }
+
+ @Test
+ public void testAppUpdateRemovesNoAppDataStorageProperty() throws Exception {
+ installPackage(TEST_HW_NO_APP_STORAGE);
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ installPackage(
+ TEST_HW5,
+ "Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Update "
+ + "attempted to change value of "
+ + "android.internal.PROPERTY_NO_APP_DATA_STORAGE");
+ }
+
+ @Test
+ public void testNoAppDataStoragePropertyCanChangeAfterUninstall() throws Exception {
+ installPackage(TEST_HW_NO_APP_STORAGE);
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ uninstallPackageSilently(TEST_APP_PACKAGE);
+ // After app is uninstalled new install can change the value of the property.
+ installPackage(TEST_HW5);
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ }
+
+ @Test
+ public void testPackageFullyRemovedBroadcastAfterUninstall() throws IOException {
+ final int currentUser = getContext().getUserId();
+ // Start another user and install this test itself for that user
+ mSecondUser = createUser("Another User");
+ assertTrue(startUser(mSecondUser));
+ installExistingPackageAsUser(getContext().getPackageName(), mSecondUser);
+ installPackage(TEST_HW5);
+ assertTrue(isAppInstalledForUser(getContext().getPackageName(), currentUser));
+ assertTrue(isAppInstalledForUser(getContext().getPackageName(), mSecondUser));
+ assertTrue(isAppInstalledForUser(TEST_APP_PACKAGE, currentUser));
+ assertTrue(isAppInstalledForUser(TEST_APP_PACKAGE, mSecondUser));
+ final FullyRemovedBroadcastReceiver broadcastReceiverForCurrentUser =
+ new FullyRemovedBroadcastReceiver(TEST_APP_PACKAGE, currentUser);
+ final FullyRemovedBroadcastReceiver broadcastReceiverForSecondUser =
+ new FullyRemovedBroadcastReceiver(TEST_APP_PACKAGE, mSecondUser);
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+ intentFilter.addDataScheme("package");
+ getContext().registerReceiver(
+ broadcastReceiverForCurrentUser, intentFilter, RECEIVER_EXPORTED);
+ getUiAutomation().adoptShellPermissionIdentity(
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ try {
+ getContext().createContextAsUser(UserHandle.of(mSecondUser), 0).registerReceiver(
+ broadcastReceiverForSecondUser, intentFilter, RECEIVER_EXPORTED);
+ } finally {
+ getUiAutomation().dropShellPermissionIdentity();
+ }
+ // Verify that uninstall with "keep data" doesn't send the broadcast
+ uninstallPackageWithKeepData(TEST_APP_PACKAGE, mSecondUser);
+ assertFalse(broadcastReceiverForSecondUser.isBroadcastReceived());
+ installExistingPackageAsUser(TEST_APP_PACKAGE, mSecondUser);
+ // Verify that uninstall on a specific user only sends the broadcast to the user
+ uninstallPackageAsUser(TEST_APP_PACKAGE, mSecondUser);
+ assertTrue(broadcastReceiverForSecondUser.isBroadcastReceived());
+ assertFalse(broadcastReceiverForCurrentUser.isBroadcastReceived());
+ uninstallPackageSilently(TEST_APP_PACKAGE);
+ assertTrue(broadcastReceiverForCurrentUser.isBroadcastReceived());
+ }
+
+ private static class FullyRemovedBroadcastReceiver extends BroadcastReceiver {
+ private final String mTargetPackage;
+ private final int mTargetUserId;
+ private final ConditionVariable mUserReceivedBroadcast;
+ FullyRemovedBroadcastReceiver(String packageName, int targetUserId) {
+ mTargetPackage = packageName;
+ mTargetUserId = targetUserId;
+ mUserReceivedBroadcast = new ConditionVariable();
+ mUserReceivedBroadcast.close();
+ }
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ context.unregisterReceiver(this);
+ final String packageName = intent.getData().getEncodedSchemeSpecificPart();
+ final int userId = context.getUserId();
+ if (intent.getAction().equals(Intent.ACTION_PACKAGE_FULLY_REMOVED)
+ && packageName.equals(mTargetPackage) && userId == mTargetUserId) {
+ mUserReceivedBroadcast.open();
+ }
+ }
+ public boolean isBroadcastReceived() {
+ return mUserReceivedBroadcast.block(2000);
+ }
+ }
+
+ private List<SharedLibraryInfo> getSharedLibraries() {
+ getUiAutomation().adoptShellPermissionIdentity();
+ try {
+ return getPackageManager().getSharedLibraries(PackageManager.PackageInfoFlags.of(0));
+ } finally {
+ getUiAutomation().dropShellPermissionIdentity();
+ }
+ }
+
+ private SharedLibraryInfo findLibrary(List<SharedLibraryInfo> libs, String name, long version) {
+ for (int i = 0, size = libs.size(); i < size; ++i) {
+ SharedLibraryInfo lib = libs.get(i);
+ if (name.equals(lib.getName()) && version == lib.getLongVersion()) {
+ return lib;
+ }
+ }
+ return null;
+ }
+
private String createUpdateSession(String packageName) throws IOException {
return createSession("-p " + packageName);
}
@@ -631,6 +1574,39 @@
.anyMatch(line -> line.substring(prefixLength).equals(packageName));
}
+ private boolean isAppInstalledForUser(String packageName, int userId) throws IOException {
+ final String commandResult = executeShellCommand(
+ String.format("pm list packages --user %d %s", userId, packageName)
+ );
+ return Arrays.stream(commandResult.split("\\r?\\n"))
+ .anyMatch(line -> line.equals("package:" + packageName));
+ }
+
+ private boolean isSdkInstalled(String name, int versionMajor) throws IOException {
+ final String sdkString = name + ":" + versionMajor;
+ final String commandResult = executeShellCommand("pm list sdks");
+ final int prefixLength = "sdk:".length();
+ return Arrays.stream(commandResult.split("\\r?\\n"))
+ .anyMatch(line -> line.length() > prefixLength && line.substring(
+ prefixLength).equals(sdkString));
+ }
+
+ private String getPackageCertDigest(String packageName) throws Exception {
+ getUiAutomation().adoptShellPermissionIdentity();
+ try {
+ PackageInfo sdkPackageInfo = getPackageManager().getPackageInfo(packageName,
+ PackageManager.PackageInfoFlags.of(
+ GET_SIGNING_CERTIFICATES | MATCH_STATIC_SHARED_AND_SDK_LIBRARIES));
+ SigningInfo signingInfo = sdkPackageInfo.signingInfo;
+ Signature[] signatures =
+ signingInfo != null ? signingInfo.getSigningCertificateHistory() : null;
+ byte[] digest = PackageUtils.computeSha256DigestBytes(signatures[0].toByteArray());
+ return new String(HexEncoding.encode(digest));
+ } finally {
+ getUiAutomation().dropShellPermissionIdentity();
+ }
+ }
+
private String getSplits(String packageName) throws IOException {
final String commandResult = executeShellCommand("pm dump " + packageName);
final String prefix = " splits=[";
@@ -648,12 +1624,34 @@
return TEST_APK_PATH + baseName;
}
+ /* Install for all the users */
private void installPackage(String baseName) throws IOException {
File file = new File(createApkPath(baseName));
assertEquals("Success\n", executeShellCommand(
"pm " + mInstall + " -t -g " + file.getPath()));
}
+ private void installPackage(String baseName, String expectedResultStartsWith)
+ throws IOException {
+ File file = new File(createApkPath(baseName));
+ String result = executeShellCommand("pm " + mInstall + " -t -g " + file.getPath());
+ assertTrue(result, result.startsWith(expectedResultStartsWith));
+ }
+
+ /* Install a package for a new user; this would replace the old package */
+ private void installPackageAsUser(String baseName, int userId) throws IOException {
+ File file = new File(createApkPath(baseName));
+ assertEquals("Success\n", executeShellCommand(
+ "pm " + mInstall + " -t -g --user " + userId + " " + file.getPath()));
+ }
+
+ /* Install an existing package for a new user */
+ private void installExistingPackageAsUser(String packageName, int userId) throws IOException {
+ String result = executeShellCommand(
+ String.format("pm install-existing --user %d %s", userId, packageName));
+ assertEquals("Package " + packageName + " installed for user: " + userId + "\n", result);
+ }
+
private void updatePackage(String packageName, String baseName) throws IOException {
File file = new File(createApkPath(baseName));
assertEquals("Success\n", executeShellCommand(
@@ -730,10 +1728,25 @@
splits)));
}
+ private void uninstallPackage(String packageName, String expectedResultStartsWith)
+ throws IOException {
+ String result = uninstallPackageSilently(packageName);
+ assertTrue(result, result.startsWith(expectedResultStartsWith));
+ }
+
private String uninstallPackageSilently(String packageName) throws IOException {
return executeShellCommand("pm uninstall " + packageName);
}
+ /* Uninstall for one user */
+ private void uninstallPackageAsUser(String packageName, int userId) throws IOException {
+ executeShellCommand(String.format("pm uninstall --user %d %s", userId, packageName));
+ }
+
+ private void uninstallPackageWithKeepData(String packageName, int userId) throws IOException {
+ executeShellCommand(String.format("pm uninstall -k --user %d %s", userId, packageName));
+ }
+
private void uninstallSplits(String packageName, String[] splitNames) throws IOException {
for (String splitName : splitNames) {
assertEquals("Success\n",
@@ -745,5 +1758,35 @@
assertEquals("Success\n", executeShellCommand(
"pm uninstall " + packageName + " " + String.join(" ", splitNames)));
}
+
+ private void setSystemProperty(String name, String value) throws Exception {
+ assertEquals("", executeShellCommand("setprop " + name + " " + value));
+ }
+
+ private int createUser(String name) throws IOException {
+ final String output = executeShellCommand("pm create-user " + name);
+ if (output.startsWith("Success")) {
+ return Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
+ }
+ throw new IllegalStateException(String.format("Failed to create user: %s", output));
+ }
+
+ private void removeUser(int userId) throws IOException {
+ executeShellCommand("pm remove-user " + userId);
+ }
+
+ private boolean startUser(int userId) throws IOException {
+ String cmd = "am start-user -w " + userId;
+ final String output = executeShellCommand(cmd);
+ if (output.startsWith("Error")) {
+ return false;
+ }
+ String state = executeShellCommand("am get-started-user-state " + userId);
+ return state.contains("RUNNING_UNLOCKED");
+ }
+
+ private void stopUser(int userId) throws IOException {
+ executeShellCommand("am stop-user -w -f " + userId);
+ }
}
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
index 262c687..d1abe03 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -20,6 +20,10 @@
import static android.content.pm.ApplicationInfo.FLAG_HAS_CODE;
import static android.content.pm.ApplicationInfo.FLAG_INSTALLED;
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+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 static android.content.pm.PackageManager.GET_ACTIVITIES;
import static android.content.pm.PackageManager.GET_META_DATA;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
@@ -48,10 +52,15 @@
import static org.testng.Assert.assertThrows;
import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.ServiceConnection;
import android.content.cts.MockActivity;
import android.content.cts.MockContentProvider;
import android.content.cts.MockReceiver;
@@ -64,18 +73,22 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ComponentEnabledSetting;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.net.Uri;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -83,10 +96,16 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.core.content.FileProvider;
import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.TestUtils;
+
+import junit.framework.AssertionFailedError;
import org.junit.After;
import org.junit.Before;
@@ -95,6 +114,7 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -102,6 +122,10 @@
import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -117,13 +141,16 @@
private Context mContext;
private PackageManager mPackageManager;
+ private Instrumentation mInstrumentation;
private static final String PACKAGE_NAME = "android.content.cts";
- private static final String CONTENT_PKG_NAME = "android.content.cts";
+ private static final String STUB_PACKAGE_NAME = "com.android.cts.stub";
private static final String APPLICATION_NAME = "android.content.cts.MockApplication";
private static final String ACTIVITY_ACTION_NAME = "android.intent.action.PMTEST";
private static final String MAIN_ACTION_NAME = "android.intent.action.MAIN";
private static final String SERVICE_ACTION_NAME =
- "android.content.pm.cts.activity.PMTEST_SERVICE";
+ "android.content.pm.cts.activity.PMTEST_SERVICE";
+ private static final String RECEIVER_ACTION_NAME =
+ "android.content.pm.cts.PackageManagerTest.PMTEST_RECEIVER";
private static final String GRANTED_PERMISSION_NAME = "android.permission.INTERNET";
private static final String NOT_GRANTED_PERMISSION_NAME = "android.permission.HARDWARE_TEST";
private static final String ACTIVITY_NAME = "android.content.pm.cts.TestPmActivity";
@@ -138,6 +165,7 @@
"android.content.cts.permission.TEST_DYNAMIC";
// Number of activities/activity-alias in AndroidManifest
private static final int NUM_OF_ACTIVITIES_IN_MANIFEST = 12;
+ public static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
private static final String SHIM_APEX_PACKAGE_NAME = "com.android.apex.cts.shim";
@@ -146,6 +174,8 @@
MATCH_APEX, MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS};
private static final String SAMPLE_APK_BASE = "/data/local/tmp/cts/content/";
+ private static final String EMPTY_APP_APK = SAMPLE_APK_BASE
+ + "CtsContentEmptyTestApp.apk";
private static final String LONG_PACKAGE_NAME_APK = SAMPLE_APK_BASE
+ "CtsContentLongPackageNameTestApp.apk";
private static final String LONG_SHARED_USER_ID_APK = SAMPLE_APK_BASE
@@ -164,13 +194,48 @@
private static final String SHELL_PACKAGE_NAME = "com.android.shell";
private static final String HELLO_WORLD_PACKAGE_NAME = "com.example.helloworld";
private static final String HELLO_WORLD_APK = SAMPLE_APK_BASE + "HelloWorld5.apk";
+ private static final String MOCK_LAUNCHER_PACKAGE_NAME = "android.content.cts.mocklauncherapp";
+ private static final String MOCK_LAUNCHER_APK = SAMPLE_APK_BASE
+ + "CtsContentMockLauncherTestApp.apk";
+ private static final String NON_EXISTENT_PACKAGE_NAME = "android.content.cts.nonexistent.pkg";
+ private static final String STUB_PACKAGE_APK = SAMPLE_APK_BASE
+ + "CtsSyncAccountAccessStubs.apk";
private static final int MAX_SAFE_LABEL_LENGTH = 1000;
+ // For intent resolution tests
+ private static final String NON_EXISTENT_ACTION_NAME = "android.intent.action.cts.NON_EXISTENT";
+ private static final String INTENT_RESOLUTION_TEST_PKG_NAME =
+ "android.content.cts.IntentResolutionTest";
+ private static final String RESOLUTION_TEST_ACTION_NAME =
+ "android.intent.action.RESOLUTION_TEST";
+ private static final String SELECTOR_ACTION_NAME = "android.intent.action.SELECTORTEST";
+ private static final String FILE_PROVIDER_AUTHORITY = "android.content.cts.fileprovider";
+
+ private static final ComponentName ACTIVITY_COMPONENT = new ComponentName(
+ PACKAGE_NAME, ACTIVITY_NAME);
+ private static final ComponentName SERVICE_COMPONENT = new ComponentName(
+ PACKAGE_NAME, SERVICE_NAME);
+ private static final ComponentName STUB_ACTIVITY_COMPONENT = ComponentName.createRelative(
+ STUB_PACKAGE_NAME, ".StubActivity");
+ private static final ComponentName STUB_SERVICE_COMPONENT = ComponentName.createRelative(
+ STUB_PACKAGE_NAME, ".StubService");
+ private static final ComponentName RESET_ENABLED_SETTING_ACTIVITY_COMPONENT =
+ ComponentName.createRelative(MOCK_LAUNCHER_PACKAGE_NAME, ".MockActivity");
+ private static final ComponentName RESET_ENABLED_SETTING_RECEIVER_COMPONENT =
+ ComponentName.createRelative(MOCK_LAUNCHER_PACKAGE_NAME, ".MockReceiver");
+ private static final ComponentName RESET_ENABLED_SETTING_SERVICE_COMPONENT =
+ ComponentName.createRelative(MOCK_LAUNCHER_PACKAGE_NAME, ".MockService");
+ private static final ComponentName RESET_ENABLED_SETTING_PROVIDER_COMPONENT =
+ ComponentName.createRelative(MOCK_LAUNCHER_PACKAGE_NAME, ".MockProvider");
+
+ private final ServiceTestRule mServiceTestRule = new ServiceTestRule();
+
@Before
public void setup() throws Exception {
mContext = InstrumentationRegistry.getContext();
mPackageManager = mContext.getPackageManager();
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
}
@After
@@ -178,6 +243,7 @@
uninstallPackage(EMPTY_APP_PACKAGE_NAME);
uninstallPackage(EMPTY_APP_MAX_PACKAGE_NAME);
uninstallPackage(HELLO_WORLD_PACKAGE_NAME);
+ uninstallPackage(MOCK_LAUNCHER_PACKAGE_NAME);
}
@Test
@@ -188,17 +254,20 @@
String cmpActivityName = "android.content.pm.cts.TestPmCompare";
// List with different activities and the filter doesn't work,
List<ResolveInfo> listWithDiff = mPackageManager.queryIntentActivityOptions(
- new ComponentName(PACKAGE_NAME, cmpActivityName), null, activityIntent, 0);
+ new ComponentName(PACKAGE_NAME, cmpActivityName), null, activityIntent,
+ PackageManager.ResolveInfoFlags.of(0));
checkActivityInfoName(ACTIVITY_NAME, listWithDiff);
// List with the same activities to make filter work
List<ResolveInfo> listInSame = mPackageManager.queryIntentActivityOptions(
- new ComponentName(PACKAGE_NAME, ACTIVITY_NAME), null, activityIntent, 0);
+ new ComponentName(PACKAGE_NAME, ACTIVITY_NAME), null, activityIntent,
+ PackageManager.ResolveInfoFlags.of(0));
assertEquals(0, listInSame.size());
// Test queryIntentActivities
List<ResolveInfo> intentActivities =
- mPackageManager.queryIntentActivities(activityIntent, 0);
+ mPackageManager.queryIntentActivities(activityIntent,
+ PackageManager.ResolveInfoFlags.of(0));
assertTrue(intentActivities.size() > 0);
checkActivityInfoName(ACTIVITY_NAME, intentActivities);
@@ -213,14 +282,14 @@
// Test queryIntentServices
Intent serviceIntent = new Intent(SERVICE_ACTION_NAME);
List<ResolveInfo> services = mPackageManager.queryIntentServices(serviceIntent,
- 0 /*flags*/);
+ PackageManager.ResolveInfoFlags.of(0));
checkServiceInfoName(SERVICE_NAME, services);
// Test queryBroadcastReceivers
- String receiverActionName = "android.content.pm.cts.PackageManagerTest.PMTEST_RECEIVER";
- Intent broadcastIntent = new Intent(receiverActionName);
- List<ResolveInfo> broadcastReceivers = new ArrayList<ResolveInfo>();
- broadcastReceivers = mPackageManager.queryBroadcastReceivers(broadcastIntent, 0);
+ Intent broadcastIntent = new Intent(RECEIVER_ACTION_NAME);
+ List<ResolveInfo> broadcastReceivers =
+ mPackageManager.queryBroadcastReceivers(broadcastIntent,
+ PackageManager.ResolveInfoFlags.of(0));
checkActivityInfoName(RECEIVER_NAME, broadcastReceivers);
// Test queryPermissionsByGroup, queryContentProviders
@@ -229,12 +298,208 @@
testPermissionsGroup, PackageManager.GET_META_DATA);
checkPermissionInfoName(CALL_ABROAD_PERMISSION_NAME, permissions);
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(0));
List<ProviderInfo> providers = mPackageManager.queryContentProviders(PACKAGE_NAME,
- appInfo.uid, 0);
+ appInfo.uid, PackageManager.ComponentInfoFlags.of(0));
checkProviderInfoName(PROVIDER_NAME, providers);
}
+ @Test
+ public void testEnforceIntentToMatchIntentFilter() {
+ Intent intent = new Intent();
+ List<ResolveInfo> results;
+
+ /* Implicit intent tests */
+
+ intent.setPackage(INTENT_RESOLUTION_TEST_PKG_NAME);
+
+ // Implicit intents with matching intent filter
+ intent.setAction(RESOLUTION_TEST_ACTION_NAME);
+ results = mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+ results = mPackageManager.queryIntentServices(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+ results = mPackageManager.queryBroadcastReceivers(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+
+ // Implicit intents with non-matching intent filter
+ intent.setAction(NON_EXISTENT_ACTION_NAME);
+ results = mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+ results = mPackageManager.queryIntentServices(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+ results = mPackageManager.queryBroadcastReceivers(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+
+ /* Explicit intent tests */
+
+ intent = new Intent();
+ ComponentName comp;
+
+ // Explicit intents with matching intent filter
+ intent.setAction(RESOLUTION_TEST_ACTION_NAME);
+ comp = new ComponentName(INTENT_RESOLUTION_TEST_PKG_NAME, ACTIVITY_NAME);
+ intent.setComponent(comp);
+ results = mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+ comp = new ComponentName(INTENT_RESOLUTION_TEST_PKG_NAME, SERVICE_NAME);
+ intent.setComponent(comp);
+ results = mPackageManager.queryIntentServices(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+ comp = new ComponentName(INTENT_RESOLUTION_TEST_PKG_NAME, RECEIVER_NAME);
+ intent.setComponent(comp);
+ results = mPackageManager.queryBroadcastReceivers(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+
+ // Explicit intents with non-matching intent filter on target T+
+ intent.setAction(NON_EXISTENT_ACTION_NAME);
+ comp = new ComponentName(INTENT_RESOLUTION_TEST_PKG_NAME, ACTIVITY_NAME);
+ intent.setComponent(comp);
+ results = mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+ comp = new ComponentName(INTENT_RESOLUTION_TEST_PKG_NAME, SERVICE_NAME);
+ intent.setComponent(comp);
+ results = mPackageManager.queryIntentServices(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+ comp = new ComponentName(INTENT_RESOLUTION_TEST_PKG_NAME, RECEIVER_NAME);
+ intent.setComponent(comp);
+ results = mPackageManager.queryBroadcastReceivers(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+
+ // More comprehensive intent matching tests on target T+
+ intent = new Intent();
+ comp = new ComponentName(INTENT_RESOLUTION_TEST_PKG_NAME, ACTIVITY_NAME);
+ intent.setComponent(comp);
+ intent.setAction(RESOLUTION_TEST_ACTION_NAME + "2");
+ results = mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+ intent.setType("*/*");
+ results = mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+ intent.setData(Uri.parse("http://example.com"));
+ results = mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+ intent.setDataAndType(Uri.parse("http://example.com"), "*/*");
+ results = mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+ File file = new File(mContext.getFilesDir(), "test.txt");
+ try {
+ file.createNewFile();
+ } catch (IOException e) {
+ fail(e.getMessage());
+ }
+ Uri uri = FileProvider.getUriForFile(mContext, FILE_PROVIDER_AUTHORITY, file);
+ intent.setData(uri);
+ results = mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+ file.delete();
+ intent.addCategory(Intent.CATEGORY_APP_BROWSER);
+ results = mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+
+ // Explicit intents with non-matching intent filter on target < T
+ final String api30Pkg = INTENT_RESOLUTION_TEST_PKG_NAME + "Api30";
+ intent.setAction(NON_EXISTENT_ACTION_NAME);
+ comp = new ComponentName(api30Pkg, ACTIVITY_NAME);
+ intent.setComponent(comp);
+ results = mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+ comp = new ComponentName(api30Pkg, SERVICE_NAME);
+ intent.setComponent(comp);
+ results = mPackageManager.queryIntentServices(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+ comp = new ComponentName(api30Pkg, RECEIVER_NAME);
+ intent.setComponent(comp);
+ results = mPackageManager.queryBroadcastReceivers(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+
+ // Explicit intents with non-matching intent filter on our own package
+ intent.setAction(NON_EXISTENT_ACTION_NAME);
+ comp = new ComponentName(PACKAGE_NAME, ACTIVITY_NAME);
+ intent.setComponent(comp);
+ results = mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+ comp = new ComponentName(PACKAGE_NAME, SERVICE_NAME);
+ intent.setComponent(comp);
+ results = mPackageManager.queryIntentServices(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+ comp = new ComponentName(PACKAGE_NAME, RECEIVER_NAME);
+ intent.setComponent(comp);
+ results = mPackageManager.queryBroadcastReceivers(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+
+ /* Intent selector tests */
+
+ Intent selector = new Intent();
+ selector.setPackage(INTENT_RESOLUTION_TEST_PKG_NAME);
+ intent = new Intent();
+ intent.setSelector(selector);
+
+ // Matching intent and matching selector
+ selector.setAction(SELECTOR_ACTION_NAME);
+ intent.setAction(RESOLUTION_TEST_ACTION_NAME);
+ results = mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+ results = mPackageManager.queryIntentServices(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+ results = mPackageManager.queryBroadcastReceivers(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(1, results.size());
+
+ // Matching intent and non-matching selector
+ selector.setAction(NON_EXISTENT_ACTION_NAME);
+ intent.setAction(RESOLUTION_TEST_ACTION_NAME);
+ results = mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+ results = mPackageManager.queryIntentServices(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+ results = mPackageManager.queryBroadcastReceivers(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+
+ // Non-matching intent and matching selector
+ selector.setAction(SELECTOR_ACTION_NAME);
+ intent.setAction(NON_EXISTENT_ACTION_NAME);
+ results = mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+ results = mPackageManager.queryIntentServices(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+ results = mPackageManager.queryBroadcastReceivers(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+ }
+
private void checkActivityInfoName(String expectedName, List<ResolveInfo> resolves) {
// Flag for checking if the name is contained in list array.
boolean isContained = false;
@@ -305,7 +570,8 @@
@Test
public void testGetInfo() throws NameNotFoundException {
// Test getApplicationInfo, getText
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(0));
int discriptionRes = R.string.hello_android;
String expectedDisciptionRes = "Hello, Android!";
CharSequence appText = mPackageManager.getText(PACKAGE_NAME, discriptionRes, appInfo);
@@ -317,7 +583,7 @@
// Test getPackageInfo
PackageInfo packageInfo = mPackageManager.getPackageInfo(PACKAGE_NAME,
- PackageManager.GET_INSTRUMENTATION);
+ PackageManager.PackageInfoFlags.of(PackageManager.GET_INSTRUMENTATION));
assertEquals(PACKAGE_NAME, packageInfo.packageName);
// Test getApplicationInfo, getApplicationLabel
@@ -327,22 +593,25 @@
// Test getServiceInfo
assertEquals(SERVICE_NAME, mPackageManager.getServiceInfo(serviceName,
- PackageManager.GET_META_DATA).name);
+ PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA)).name);
// Test getReceiverInfo
- assertEquals(RECEIVER_NAME, mPackageManager.getReceiverInfo(receiverName, 0).name);
+ assertEquals(RECEIVER_NAME, mPackageManager.getReceiverInfo(receiverName,
+ PackageManager.ComponentInfoFlags.of(0)).name);
// Test getPackageArchiveInfo
final String apkRoute = mContext.getPackageCodePath();
final String apkName = mContext.getPackageName();
- assertEquals(apkName, mPackageManager.getPackageArchiveInfo(apkRoute, 0).packageName);
+ assertEquals(apkName, mPackageManager.getPackageArchiveInfo(apkRoute,
+ PackageManager.PackageInfoFlags.of(0)).packageName);
// Test getPackagesForUid, getNameForUid
checkPackagesNameForUid(PACKAGE_NAME, mPackageManager.getPackagesForUid(appInfo.uid));
assertEquals(PACKAGE_NAME, mPackageManager.getNameForUid(appInfo.uid));
// Test getActivityInfo
- assertEquals(ACTIVITY_NAME, mPackageManager.getActivityInfo(activityName, 0).name);
+ assertEquals(ACTIVITY_NAME, mPackageManager.getActivityInfo(activityName,
+ PackageManager.ComponentInfoFlags.of(0)).name);
// Test getPackageGids
assertTrue(mPackageManager.getPackageGids(PACKAGE_NAME).length > 0);
@@ -360,10 +629,12 @@
checkPermissionGroupInfoName(PERMISSIONGROUP_NAME, permissionGroups);
// Test getInstalledApplications
- assertTrue(mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA).size() > 0);
+ assertTrue(mPackageManager.getInstalledApplications(
+ PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA)).size() > 0);
// Test getInstalledPacakge
- assertTrue(mPackageManager.getInstalledPackages(0).size() > 0);
+ assertTrue(mPackageManager.getInstalledPackages(
+ PackageManager.PackageInfoFlags.of(0)).size() > 0);
// Test getInstrumentationInfo
assertEquals(INSTRUMENT_NAME, mPackageManager.getInstrumentationInfo(instrName, 0).name);
@@ -383,8 +654,8 @@
assertFalse(mPackageManager.isSafeMode());
// Test getTargetSdkVersion
- int expectedTargetSdk = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0)
- .targetSdkVersion;
+ int expectedTargetSdk = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(0)).targetSdkVersion;
assertEquals(expectedTargetSdk, mPackageManager.getTargetSdkVersion(PACKAGE_NAME));
assertThrows(PackageManager.NameNotFoundException.class,
() -> mPackageManager.getTargetSdkVersion(
@@ -422,6 +693,7 @@
* Simple test for {@link PackageManager#getPreferredActivities(List, List, String)} that tests
* calling it has no effect. The method is essentially a no-op because no preferred activities
* can be added.
+ *
* @see PackageManager#addPreferredActivity(IntentFilter, int, ComponentName[], ComponentName)
*/
@Test
@@ -508,14 +780,14 @@
@Test
public void testAccessEnabledSetting() {
mPackageManager.setApplicationEnabledSetting(PACKAGE_NAME,
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
- assertEquals(PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
+ assertEquals(COMPONENT_ENABLED_STATE_ENABLED,
mPackageManager.getApplicationEnabledSetting(PACKAGE_NAME));
ComponentName componentName = new ComponentName(PACKAGE_NAME, ACTIVITY_NAME);
mPackageManager.setComponentEnabledSetting(componentName,
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
- assertEquals(PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
+ assertEquals(COMPONENT_ENABLED_STATE_ENABLED,
mPackageManager.getComponentEnabledSetting(componentName));
}
@@ -549,7 +821,8 @@
// getDrawable is called by ComponentInfo.loadIcon() which called by getActivityIcon()
// method of PackageMaganer. Here is just assurance for its functionality.
int iconRes = R.drawable.start;
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(0));
assertNotNull(mPackageManager.getDrawable(PACKAGE_NAME, iconRes, appInfo));
}
@@ -568,8 +841,10 @@
public void testCheckSignaturesMatch_byUid() throws NameNotFoundException {
// Compare the signature of this package to another package installed by this test suite
// (see AndroidTest.xml). Their signatures must match.
- int uid1 = mPackageManager.getPackageInfo(PACKAGE_NAME, 0).applicationInfo.uid;
- int uid2 = mPackageManager.getPackageInfo("com.android.cts.stub", 0).applicationInfo.uid;
+ int uid1 = mPackageManager.getPackageInfo(PACKAGE_NAME,
+ PackageManager.PackageInfoFlags.of(0)).applicationInfo.uid;
+ int uid2 = mPackageManager.getPackageInfo("com.android.cts.stub",
+ PackageManager.PackageInfoFlags.of(0)).applicationInfo.uid;
assertEquals(PackageManager.SIGNATURE_MATCH, mPackageManager.checkSignatures(uid1, uid2));
// A UID's signature should match its own signature.
@@ -586,8 +861,10 @@
@Test
public void testCheckSignaturesNoMatch_byUid() throws NameNotFoundException {
// This test package's signature shouldn't match the system's signature.
- int uid1 = mPackageManager.getPackageInfo(PACKAGE_NAME, 0).applicationInfo.uid;
- int uid2 = mPackageManager.getPackageInfo("android", 0).applicationInfo.uid;
+ int uid1 = mPackageManager.getPackageInfo(PACKAGE_NAME,
+ PackageManager.PackageInfoFlags.of(0)).applicationInfo.uid;
+ int uid2 = mPackageManager.getPackageInfo("android",
+ PackageManager.PackageInfoFlags.of(0)).applicationInfo.uid;
assertEquals(PackageManager.SIGNATURE_NO_MATCH,
mPackageManager.checkSignatures(uid1, uid2));
}
@@ -616,18 +893,21 @@
Intent intent = new Intent(ACTIVITY_ACTION_NAME);
intent.setComponent(new ComponentName(PACKAGE_NAME, ACTIVITY_NAME));
assertEquals(ACTIVITY_NAME, mPackageManager.resolveActivity(intent,
- PackageManager.MATCH_DEFAULT_ONLY).activityInfo.name);
+ PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY))
+ .activityInfo.name);
// Test resolveService
intent = new Intent(SERVICE_ACTION_NAME);
intent.setComponent(new ComponentName(PACKAGE_NAME, SERVICE_NAME));
- ResolveInfo resolveInfo = mPackageManager.resolveService(intent, 0 /*flags*/);
+ ResolveInfo resolveInfo = mPackageManager.resolveService(intent,
+ PackageManager.ResolveInfoFlags.of(0));
assertEquals(SERVICE_NAME, resolveInfo.serviceInfo.name);
// Test resolveContentProvider
String providerAuthorities = "ctstest";
assertEquals(PROVIDER_NAME,
- mPackageManager.resolveContentProvider(providerAuthorities, 0).name);
+ mPackageManager.resolveContentProvider(providerAuthorities,
+ PackageManager.ComponentInfoFlags.of(0)).name);
}
@Test
@@ -635,7 +915,8 @@
ComponentName componentName = new ComponentName(PACKAGE_NAME, ACTIVITY_NAME);
int resourceId = R.xml.pm_test;
String xmlName = "android.content.cts:xml/pm_test";
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(0));
assertNotNull(mPackageManager.getXml(PACKAGE_NAME, resourceId, appInfo));
assertEquals(xmlName, mPackageManager.getResourcesForActivity(componentName)
.getResourceName(resourceId));
@@ -648,7 +929,8 @@
@Test
public void testGetResources_withConfig() throws NameNotFoundException {
int resourceId = R.string.config_overridden_string;
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(0));
Configuration c1 = new Configuration(mContext.getResources().getConfiguration());
c1.orientation = Configuration.ORIENTATION_PORTRAIT;
@@ -668,7 +950,8 @@
final int flags = PackageManager.GET_SIGNATURES;
- final PackageInfo pkgInfo = mPackageManager.getPackageArchiveInfo(apkPath, flags);
+ final PackageInfo pkgInfo = mPackageManager.getPackageArchiveInfo(apkPath,
+ PackageManager.PackageInfoFlags.of(flags));
assertEquals("getPackageArchiveInfo should return the correct package name",
apkName, pkgInfo.packageName);
@@ -690,8 +973,9 @@
@Test
public void testGetNamesForUids_valid() throws Exception {
final int shimId =
- mPackageManager.getApplicationInfo("com.android.cts.ctsshim", 0 /*flags*/).uid;
- final int[] uids = new int[] {
+ mPackageManager.getApplicationInfo("com.android.cts.ctsshim",
+ PackageManager.ApplicationInfoFlags.of(0)).uid;
+ final int[] uids = new int[]{
1000,
Integer.MAX_VALUE,
shimId,
@@ -710,10 +994,13 @@
int userId = mContext.getUserId();
int expectedUid = UserHandle.getUid(userId, 1000);
- assertEquals(expectedUid, mPackageManager.getPackageUid("android", 0));
+ assertEquals(expectedUid, mPackageManager.getPackageUid("android",
+ PackageManager.PackageInfoFlags.of(0)));
- int uid = mPackageManager.getApplicationInfo("com.android.cts.ctsshim", 0 /*flags*/).uid;
- assertEquals(uid, mPackageManager.getPackageUid("com.android.cts.ctsshim", 0));
+ int uid = mPackageManager.getApplicationInfo("com.android.cts.ctsshim",
+ PackageManager.ApplicationInfoFlags.of(0)).uid;
+ assertEquals(uid, mPackageManager.getPackageUid("com.android.cts.ctsshim",
+ PackageManager.PackageInfoFlags.of(0)));
}
@Test
@@ -726,7 +1013,8 @@
@Test
public void testGetPackageInfo_notFound() {
try {
- mPackageManager.getPackageInfo("this.package.does.not.exist", 0);
+ mPackageManager.getPackageInfo("this.package.does.not.exist",
+ PackageManager.PackageInfoFlags.of(0));
fail("Exception expected");
} catch (NameNotFoundException expected) {
}
@@ -734,8 +1022,10 @@
@Test
public void testGetInstalledPackages() throws Exception {
- List<PackageInfo> pkgs = mPackageManager.getInstalledPackages(GET_META_DATA
- | GET_PERMISSIONS | GET_ACTIVITIES | GET_PROVIDERS | GET_SERVICES | GET_RECEIVERS);
+ List<PackageInfo> pkgs = mPackageManager.getInstalledPackages(
+ PackageManager.PackageInfoFlags.of(
+ GET_META_DATA | GET_PERMISSIONS | GET_ACTIVITIES | GET_PROVIDERS
+ | GET_SERVICES | GET_RECEIVERS));
PackageInfo pkgInfo = findPackageOrFail(pkgs, PACKAGE_NAME);
assertTestPackageInfo(pkgInfo);
@@ -819,7 +1109,8 @@
// Tests that other packages can be queried.
@Test
public void testGetInstalledPackages_OtherPackages() throws Exception {
- List<PackageInfo> pkgInfos = mPackageManager.getInstalledPackages(0);
+ List<PackageInfo> pkgInfos = mPackageManager.getInstalledPackages(
+ PackageManager.PackageInfoFlags.of(0));
// Check a normal package.
PackageInfo pkgInfo = findPackageOrFail(pkgInfos, "com.android.cts.stub"); // A test package
@@ -832,10 +1123,11 @@
@Test
public void testGetInstalledApplications() throws Exception {
- List<ApplicationInfo> apps = mPackageManager.getInstalledApplications(GET_META_DATA);
+ List<ApplicationInfo> apps = mPackageManager.getInstalledApplications(
+ PackageManager.ApplicationInfoFlags.of(GET_META_DATA));
ApplicationInfo app = findPackageItemOrFail(
- apps.toArray(new ApplicationInfo[] {}), APPLICATION_NAME);
+ apps.toArray(new ApplicationInfo[]{}), APPLICATION_NAME);
assertEquals(APPLICATION_NAME, app.name);
assertEquals("Android TestCase", app.loadLabel(mPackageManager));
@@ -868,11 +1160,12 @@
@Test
public void testGetPackagesHoldingPermissions() {
List<PackageInfo> pkgInfos = mPackageManager.getPackagesHoldingPermissions(
- new String[] { GRANTED_PERMISSION_NAME }, 0);
+ new String[]{GRANTED_PERMISSION_NAME}, PackageManager.PackageInfoFlags.of(0));
findPackageOrFail(pkgInfos, PACKAGE_NAME);
pkgInfos = mPackageManager.getPackagesHoldingPermissions(
- new String[] { NOT_GRANTED_PERMISSION_NAME }, 0);
+ new String[]{NOT_GRANTED_PERMISSION_NAME},
+ PackageManager.PackageInfoFlags.of(0));
for (PackageInfo pkgInfo : pkgInfos) {
if (PACKAGE_NAME.equals(pkgInfo.packageName)) {
fail("Must not return package " + PACKAGE_NAME);
@@ -999,7 +1292,8 @@
boolean isAppStillVisible = true;
while (SystemClock.elapsedRealtime() < startTimeMs + timeoutMs) {
try {
- mPackageManager.getPackageInfo(packageToManipulate, MATCH_SYSTEM_ONLY);
+ mPackageManager.getPackageInfo(packageToManipulate,
+ PackageManager.PackageInfoFlags.of(MATCH_SYSTEM_ONLY));
} catch (NameNotFoundException e) {
// expected, stop polling
isAppStillVisible = false;
@@ -1018,7 +1312,8 @@
mPackageManager.setSystemAppState(packageToManipulate,
PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_VISIBLE));
try {
- mPackageManager.getPackageInfo(packageToManipulate, MATCH_SYSTEM_ONLY);
+ mPackageManager.getPackageInfo(packageToManipulate,
+ PackageManager.PackageInfoFlags.of(MATCH_SYSTEM_ONLY));
} catch (NameNotFoundException e) {
fail(packageToManipulate
+ " should be found via getPackageInfo after re-enabling.");
@@ -1031,7 +1326,8 @@
assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
PackageInfo packageInfo = mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME,
- PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY);
+ PackageManager.PackageInfoFlags.of(
+ PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY));
assertShimApexInfoIsCorrect(packageInfo);
}
@@ -1040,7 +1336,8 @@
assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
try {
- mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME, 0 /* flags */);
+ mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME,
+ PackageManager.PackageInfoFlags.of(0));
fail("NameNotFoundException expected");
} catch (NameNotFoundException expected) {
}
@@ -1051,7 +1348,8 @@
assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
try {
- mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
+ mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME,
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
fail("NameNotFoundException expected");
} catch (NameNotFoundException expected) {
}
@@ -1062,7 +1360,8 @@
assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
try {
- mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME, 0);
+ mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME,
+ PackageManager.PackageInfoFlags.of(0));
fail("NameNotFoundException expected");
} catch (NameNotFoundException expected) {
}
@@ -1073,7 +1372,8 @@
assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(
- PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY);
+ PackageManager.PackageInfoFlags.of(
+ PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY));
List<PackageInfo> shimApex = installedPackages.stream().filter(
packageInfo -> packageInfo.packageName.equals(SHIM_APEX_PACKAGE_NAME)).collect(
Collectors.toList());
@@ -1085,7 +1385,8 @@
public void testGetInstalledPackages_ApexSupported_DoesNotMatchApex() {
assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
- List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(0);
+ List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(
+ PackageManager.PackageInfoFlags.of(0));
List<PackageInfo> shimApex = installedPackages.stream().filter(
packageInfo -> packageInfo.packageName.equals(SHIM_APEX_PACKAGE_NAME)).collect(
Collectors.toList());
@@ -1097,7 +1398,7 @@
assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(
- PackageManager.MATCH_APEX);
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
List<PackageInfo> shimApex = installedPackages.stream().filter(
packageInfo -> packageInfo.packageName.equals(SHIM_APEX_PACKAGE_NAME)).collect(
Collectors.toList());
@@ -1108,7 +1409,8 @@
public void testGetInstalledPackages_ApexNotSupported_DoesNotMatchApex() {
assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
- List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(0);
+ List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(
+ PackageManager.PackageInfoFlags.of(0));
List<PackageInfo> shimApex = installedPackages.stream().filter(
packageInfo -> packageInfo.packageName.equals(SHIM_APEX_PACKAGE_NAME)).collect(
Collectors.toList());
@@ -1122,7 +1424,8 @@
@Test
public void testGetInfo_noMetaData_InPackage() throws Exception {
final PackageInfo info = mPackageManager.getPackageInfo(PACKAGE_NAME,
- GET_ACTIVITIES | GET_SERVICES | GET_RECEIVERS | GET_PROVIDERS);
+ PackageManager.PackageInfoFlags.of(
+ GET_ACTIVITIES | GET_SERVICES | GET_RECEIVERS | GET_PROVIDERS));
assertThat(info.applicationInfo.metaData).isNull();
Arrays.stream(info.activities).forEach(i -> assertThat(i.metaData).isNull());
@@ -1137,7 +1440,8 @@
*/
@Test
public void testGetInfo_noMetaData_InApplication() throws Exception {
- final ApplicationInfo ai = mPackageManager.getApplicationInfo(PACKAGE_NAME, /* flags */ 0);
+ final ApplicationInfo ai = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(0));
assertThat(ai.metaData).isNull();
}
@@ -1148,7 +1452,8 @@
@Test
public void testGetInfo_noMetaData_InActivity() throws Exception {
final ComponentName componentName = new ComponentName(mContext, MockActivity.class);
- final ActivityInfo info = mPackageManager.getActivityInfo(componentName, /* flags */ 0);
+ final ActivityInfo info = mPackageManager.getActivityInfo(componentName,
+ PackageManager.ComponentInfoFlags.of(0));
assertThat(info.metaData).isNull();
}
@@ -1159,7 +1464,8 @@
@Test
public void testGetInfo_noMetaData_InService() throws Exception {
final ComponentName componentName = new ComponentName(mContext, MockService.class);
- final ServiceInfo info = mPackageManager.getServiceInfo(componentName, /* flags */ 0);
+ final ServiceInfo info = mPackageManager.getServiceInfo(componentName,
+ PackageManager.ComponentInfoFlags.of(0));
assertThat(info.metaData).isNull();
}
@@ -1170,7 +1476,8 @@
@Test
public void testGetInfo_noMetaData_InBroadcastReceiver() throws Exception {
final ComponentName componentName = new ComponentName(mContext, MockReceiver.class);
- final ActivityInfo info = mPackageManager.getReceiverInfo(componentName, /* flags */ 0);
+ final ActivityInfo info = mPackageManager.getReceiverInfo(componentName,
+ PackageManager.ComponentInfoFlags.of(0));
assertThat(info.metaData).isNull();
}
@@ -1181,7 +1488,8 @@
@Test
public void testGetInfo_noMetaData_InContentProvider() throws Exception {
final ComponentName componentName = new ComponentName(mContext, MockContentProvider.class);
- final ProviderInfo info = mPackageManager.getProviderInfo(componentName, /* flags */ 0);
+ final ProviderInfo info = mPackageManager.getProviderInfo(componentName,
+ PackageManager.ComponentInfoFlags.of(0));
assertThat(info.metaData).isNull();
}
@@ -1192,7 +1500,9 @@
@Test
public void testGetInfo_checkMetaData_InPackage() throws Exception {
final PackageInfo info = mPackageManager.getPackageInfo(PACKAGE_NAME,
- GET_META_DATA | GET_ACTIVITIES | GET_SERVICES | GET_RECEIVERS | GET_PROVIDERS);
+ PackageManager.PackageInfoFlags.of(
+ GET_META_DATA | GET_ACTIVITIES | GET_SERVICES | GET_RECEIVERS
+ | GET_PROVIDERS));
checkMetaData(new PackageItemInfo(info.applicationInfo));
checkMetaData(new PackageItemInfo(
@@ -1211,7 +1521,8 @@
*/
@Test
public void testGetInfo_checkMetaData_InApplication() throws Exception {
- final ApplicationInfo ai = mPackageManager.getApplicationInfo(PACKAGE_NAME, GET_META_DATA);
+ final ApplicationInfo ai = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(GET_META_DATA));
checkMetaData(new PackageItemInfo(ai));
}
@@ -1222,7 +1533,8 @@
@Test
public void testGetInfo_checkMetaData_InActivity() throws Exception {
final ComponentName componentName = new ComponentName(mContext, MockActivity.class);
- final ActivityInfo ai = mPackageManager.getActivityInfo(componentName, GET_META_DATA);
+ final ActivityInfo ai = mPackageManager.getActivityInfo(componentName,
+ PackageManager.ComponentInfoFlags.of(GET_META_DATA));
checkMetaData(new PackageItemInfo(ai));
}
@@ -1233,7 +1545,8 @@
@Test
public void testGetInfo_checkMetaData_InService() throws Exception {
final ComponentName componentName = new ComponentName(mContext, MockService.class);
- final ServiceInfo info = mPackageManager.getServiceInfo(componentName, GET_META_DATA);
+ final ServiceInfo info = mPackageManager.getServiceInfo(componentName,
+ PackageManager.ComponentInfoFlags.of(GET_META_DATA));
checkMetaData(new PackageItemInfo(info));
}
@@ -1244,7 +1557,8 @@
@Test
public void testGetInfo_checkMetaData_InBroadcastReceiver() throws Exception {
final ComponentName componentName = new ComponentName(mContext, MockReceiver.class);
- final ActivityInfo info = mPackageManager.getReceiverInfo(componentName, GET_META_DATA);
+ final ActivityInfo info = mPackageManager.getReceiverInfo(componentName,
+ PackageManager.ComponentInfoFlags.of(GET_META_DATA));
checkMetaData(new PackageItemInfo(info));
}
@@ -1255,7 +1569,8 @@
@Test
public void testGetInfo_checkMetaData_InContentProvider() throws Exception {
final ComponentName componentName = new ComponentName(mContext, MockContentProvider.class);
- final ProviderInfo info = mPackageManager.getProviderInfo(componentName, GET_META_DATA);
+ final ProviderInfo info = mPackageManager.getProviderInfo(componentName,
+ PackageManager.ComponentInfoFlags.of(GET_META_DATA));
checkMetaData(new PackageItemInfo(info));
}
@@ -1290,7 +1605,7 @@
assertThat(xml.getAttributeIntValue(null, "rawColor", 0)).isEqualTo(0xffffff00);
assertThat(xml.getAttributeValue(null, "rawColor")).isEqualTo("#ffffff00");
- a = res.obtainAttributes(xml, new int[] {android.R.attr.text, android.R.attr.color});
+ a = res.obtainAttributes(xml, new int[]{android.R.attr.text, android.R.attr.color});
assertThat(a.getString(0)).isEqualTo("metadata text");
assertThat(a.getColor(1, 0)).isEqualTo(0xffff0000);
assertThat(a.getString(1)).isEqualTo("#ffff0000");
@@ -1309,7 +1624,8 @@
assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
ApplicationInfo ai = mPackageManager.getApplicationInfo(
- SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
+ SHIM_APEX_PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_APEX));
assertThat(ai.sourceDir).isEqualTo("/system/apex/com.android.apex.cts.shim.apex");
assertThat(ai.publicSourceDir).isEqualTo(ai.sourceDir);
assertThat(ai.flags & ApplicationInfo.FLAG_SYSTEM).isEqualTo(ApplicationInfo.FLAG_SYSTEM);
@@ -1323,7 +1639,7 @@
final boolean useRoundIcon = mContext.getResources().getBoolean(
mContext.getResources().getIdentifier("config_useRoundIcon", "bool", "android"));
final ApplicationInfo info = mPackageManager.getApplicationInfo(HELLO_WORLD_PACKAGE_NAME,
- 0 /*flags*/);
+ PackageManager.ApplicationInfoFlags.of(0));
assertThat(info.icon).isEqualTo((useRoundIcon ? info.roundIconRes : info.iconRes));
}
@@ -1348,8 +1664,9 @@
/**
* Runs a test for all combinations of a set of flags
+ *
* @param flagValues Which flags to use
- * @param test The test
+ * @param test The test
*/
public void runTestWithFlags(int[] flagValues, Consumer<Integer> test) {
for (int i = 0; i < (1 << flagValues.length); i++) {
@@ -1377,10 +1694,12 @@
runTestWithFlags(PACKAGE_INFO_MATCH_FLAGS,
this::testGetInstalledPackages_WithFactoryFlag_IsSubset);
}
+
public void testGetInstalledPackages_WithFactoryFlag_IsSubset(int flags) {
- List<PackageInfo> packageInfos = mPackageManager.getInstalledPackages(flags);
+ List<PackageInfo> packageInfos = mPackageManager.getInstalledPackages(
+ PackageManager.PackageInfoFlags.of(flags));
List<PackageInfo> packageInfos2 = mPackageManager.getInstalledPackages(
- flags | MATCH_FACTORY_ONLY);
+ PackageManager.PackageInfoFlags.of(flags | MATCH_FACTORY_ONLY));
Set<String> supersetNames =
packageInfos.stream().map(pi -> pi.packageName).collect(Collectors.toSet());
@@ -1401,9 +1720,11 @@
runTestWithFlags(PACKAGE_INFO_MATCH_FLAGS,
this::testGetInstalledPackages_WithFactoryFlag_ImpliesSystem);
}
+
public void testGetInstalledPackages_WithFactoryFlag_ImpliesSystem(int flags) {
List<PackageInfo> packageInfos =
- mPackageManager.getInstalledPackages(flags | MATCH_FACTORY_ONLY);
+ mPackageManager.getInstalledPackages(
+ PackageManager.PackageInfoFlags.of(flags | MATCH_FACTORY_ONLY));
for (PackageInfo pi : packageInfos) {
if (!pi.applicationInfo.isSystemApp()) {
throw new AssertionError(pi.packageName + " is not a system app.");
@@ -1421,25 +1742,10 @@
this::testGetInstalledPackages_WithFactoryFlag_ContainsNoDuplicates);
}
- // TODO(b/200519752): Remove once the bug is fixed
- private boolean containsUpdatedApex() {
- List<PackageInfo> installedApexPackages =
- mPackageManager.getInstalledPackages(PackageManager.MATCH_APEX);
- return installedApexPackages.stream().anyMatch(
- p -> p.applicationInfo.sourceDir.startsWith("/data/apex"));
- }
-
public void testGetInstalledPackages_WithFactoryFlag_ContainsNoDuplicates(int flags) {
- // TODO(b/200519752): Due to the bug, if there are updated APEX modules, then test will fail
- // for flag: 0x40002000 and its superset. Skip under that specific condition.
- int flagToSkip = MATCH_UNINSTALLED_PACKAGES | MATCH_APEX;
- if (containsUpdatedApex() && (flags & flagToSkip) == flagToSkip) {
- // Return silently so that the test still gets run for other flag combination.
- return;
- }
-
List<PackageInfo> packageInfos =
- mPackageManager.getInstalledPackages(flags | MATCH_FACTORY_ONLY);
+ mPackageManager.getInstalledPackages(
+ PackageManager.PackageInfoFlags.of(flags | MATCH_FACTORY_ONLY));
Set<String> foundPackages = new HashSet<>();
for (PackageInfo pi : packageInfos) {
if (foundPackages.contains(pi.packageName)) {
@@ -1452,7 +1758,7 @@
@Test
public void testInstallTestOnlyPackagePermission_onlyGrantedToShell() {
List<PackageInfo> packages = mPackageManager.getPackagesHoldingPermissions(
- new String[]{INSTALL_TEST_ONLY_PACKAGE}, /* flags= */ 0);
+ new String[]{INSTALL_TEST_ONLY_PACKAGE}, PackageManager.PackageInfoFlags.of(0));
assertThat(packages).hasSize(1);
assertThat(packages.get(0).packageName).isEqualTo(SHELL_PACKAGE_NAME);
@@ -1479,7 +1785,12 @@
}
private boolean installPackage(String apkPath) {
- return SystemUtil.runShellCommand("pm install -t " + apkPath).equals("Success\n");
+ return installPackage(apkPath, false /* dontKill */);
+ }
+
+ private boolean installPackage(String apkPath, boolean dontKill) {
+ return SystemUtil.runShellCommand(
+ "pm install -t " + (dontKill ? "--dont-kill " : "") + apkPath).equals("Success\n");
}
private void uninstallPackage(String packageName) {
@@ -1487,10 +1798,117 @@
}
@Test
+ public void testGetLaunchIntentSenderForPackage() throws Exception {
+ final Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+ LauncherMockActivity.class.getName(), null /* result */, false /* block */);
+ mInstrumentation.addMonitor(monitor);
+
+ try {
+ final IntentSender intentSender = mPackageManager.getLaunchIntentSenderForPackage(
+ PACKAGE_NAME);
+ assertThat(intentSender.getCreatorPackage()).isEqualTo(PACKAGE_NAME);
+ assertThat(intentSender.getCreatorUid()).isEqualTo(mContext.getApplicationInfo().uid);
+
+ intentSender.sendIntent(mContext, 0 /* code */, null /* intent */,
+ null /* onFinished */, null /* handler */);
+ final Activity activity = monitor.waitForActivityWithTimeout(TIMEOUT_MS);
+ assertThat(activity).isNotNull();
+ activity.finish();
+ } finally {
+ mInstrumentation.removeMonitor(monitor);
+ }
+ }
+
+ @Test(expected = IntentSender.SendIntentException.class)
+ public void testGetLaunchIntentSenderForPackage_noMainActivity() throws Exception {
+ assertThat(installPackage(EMPTY_APP_APK)).isTrue();
+ final PackageInfo packageInfo = mPackageManager.getPackageInfo(EMPTY_APP_PACKAGE_NAME,
+ PackageManager.PackageInfoFlags.of(0));
+ assertThat(packageInfo.packageName).isEqualTo(EMPTY_APP_PACKAGE_NAME);
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setPackage(EMPTY_APP_PACKAGE_NAME);
+ assertThat(mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0))).isEmpty();
+
+ final IntentSender intentSender = mPackageManager.getLaunchIntentSenderForPackage(
+ EMPTY_APP_PACKAGE_NAME);
+ assertThat(intentSender.getCreatorPackage()).isEqualTo(PACKAGE_NAME);
+ assertThat(intentSender.getCreatorUid()).isEqualTo(mContext.getApplicationInfo().uid);
+
+ intentSender.sendIntent(mContext, 0 /* code */, null /* intent */,
+ null /* onFinished */, null /* handler */);
+ }
+
+ @Test(expected = IntentSender.SendIntentException.class)
+ public void testGetLaunchIntentSenderForPackage_packageNotExist() throws Exception {
+ try {
+ mPackageManager.getPackageInfo(EMPTY_APP_PACKAGE_NAME,
+ PackageManager.PackageInfoFlags.of(0));
+ fail(EMPTY_APP_PACKAGE_NAME + " should not exist in the device");
+ } catch (NameNotFoundException e) {
+ }
+ final IntentSender intentSender = mPackageManager.getLaunchIntentSenderForPackage(
+ EMPTY_APP_PACKAGE_NAME);
+ assertThat(intentSender.getCreatorPackage()).isEqualTo(PACKAGE_NAME);
+ assertThat(intentSender.getCreatorUid()).isEqualTo(mContext.getApplicationInfo().uid);
+
+ intentSender.sendIntent(mContext, 0 /* code */, null /* intent */,
+ null /* onFinished */, null /* handler */);
+ }
+
+ @Test
+ public void testDefaultHomeActivity_doesntChange_whenInstallAnotherLauncher() throws Exception {
+ final Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME);
+ final String currentHomeActivity =
+ mPackageManager.resolveActivity(homeIntent,
+ PackageManager.ResolveInfoFlags.of(0)).activityInfo.name;
+
+ // Install another launcher app.
+ assertThat(installPackage(MOCK_LAUNCHER_APK)).isTrue();
+
+ // There is an async operation to re-set the default home activity in Role with no way
+ // to listen for completion once a package installed, so poll until the default home
+ // activity is set.
+ PollingCheck.waitFor(() -> currentHomeActivity.equals(
+ mPackageManager.resolveActivity(homeIntent,
+ PackageManager.ResolveInfoFlags.of(0)).activityInfo.name));
+ final List<String> homeApps =
+ mPackageManager.queryIntentActivities(homeIntent,
+ PackageManager.ResolveInfoFlags.of(0)).stream()
+ .map(i -> i.activityInfo.packageName).collect(Collectors.toList());
+ assertThat(homeApps.contains(MOCK_LAUNCHER_PACKAGE_NAME)).isTrue();
+ }
+
+ @Test
+ public void setComponentEnabledSetting_nonExistentPackage_withoutPermission() {
+ final ComponentName componentName = ComponentName.createRelative(
+ NON_EXISTENT_PACKAGE_NAME, "ClassName");
+ assertThrows(SecurityException.class, () -> mPackageManager.setComponentEnabledSetting(
+ componentName, COMPONENT_ENABLED_STATE_ENABLED, 0 /* flags */));
+ }
+
+ @Test
+ public void setComponentEnabledSetting_nonExistentPackage_hasPermission() {
+ final ComponentName componentName = ComponentName.createRelative(
+ NON_EXISTENT_PACKAGE_NAME, "ClassName");
+ mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
+ android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
+
+ try {
+ assertThrows(IllegalArgumentException.class,
+ () -> mPackageManager.setComponentEnabledSetting(componentName,
+ COMPONENT_ENABLED_STATE_ENABLED, 0 /* flags */));
+ } finally {
+ mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
public void loadApplicationLabel_withLongLabelName_truncated() throws Exception {
assertThat(installPackage(LONG_LABEL_NAME_APK)).isTrue();
final ApplicationInfo info = mPackageManager.getApplicationInfo(
- EMPTY_APP_PACKAGE_NAME, 0 /* flags */);
+ EMPTY_APP_PACKAGE_NAME, PackageManager.ApplicationInfoFlags.of(0));
final CharSequence resLabel = mPackageManager.getText(
EMPTY_APP_PACKAGE_NAME, info.labelRes, info);
@@ -1504,9 +1922,9 @@
final ComponentName componentName = ComponentName.createRelative(
EMPTY_APP_PACKAGE_NAME, ".MockActivity");
final ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
- EMPTY_APP_PACKAGE_NAME, 0 /* flags */);
+ EMPTY_APP_PACKAGE_NAME, PackageManager.ApplicationInfoFlags.of(0));
final ActivityInfo activityInfo = mPackageManager.getActivityInfo(
- componentName, 0 /* flags */);
+ componentName, PackageManager.ComponentInfoFlags.of(0));
final CharSequence resLabel = mPackageManager.getText(
EMPTY_APP_PACKAGE_NAME, activityInfo.labelRes, appInfo);
@@ -1514,4 +1932,270 @@
assertThat(activityInfo.loadLabel(mPackageManager).length())
.isEqualTo(MAX_SAFE_LABEL_LENGTH);
}
+
+ @Test
+ public void setComponentEnabledSettings_withDuplicatedComponent() {
+ final List<ComponentEnabledSetting> enabledSettings = List.of(
+ new ComponentEnabledSetting(
+ ACTIVITY_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP),
+ new ComponentEnabledSetting(
+ ACTIVITY_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mPackageManager.setComponentEnabledSettings(enabledSettings));
+ }
+
+ @Test
+ public void setComponentEnabledSettings_flagDontKillAppConflict() {
+ final List<ComponentEnabledSetting> enabledSettings = List.of(
+ new ComponentEnabledSetting(
+ ACTIVITY_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP),
+ new ComponentEnabledSetting(
+ SERVICE_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, 0));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mPackageManager.setComponentEnabledSettings(enabledSettings));
+ }
+
+ @Test
+ public void setComponentEnabledSettings_disableSelfAndStubApp_withoutPermission() {
+ final List<ComponentEnabledSetting> enabledSettings = List.of(
+ new ComponentEnabledSetting(
+ ACTIVITY_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP),
+ new ComponentEnabledSetting(
+ STUB_ACTIVITY_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, 0));
+
+ assertThrows(SecurityException.class,
+ () -> mPackageManager.setComponentEnabledSettings(enabledSettings));
+ }
+
+ @Test
+ public void setComponentEnabledSettings_disableSelf() throws Exception {
+ final int activityState = mPackageManager.getComponentEnabledSetting(ACTIVITY_COMPONENT);
+ final int serviceState = mPackageManager.getComponentEnabledSetting(SERVICE_COMPONENT);
+ assertThat(activityState).isAnyOf(
+ COMPONENT_ENABLED_STATE_DEFAULT, COMPONENT_ENABLED_STATE_ENABLED);
+ assertThat(serviceState).isAnyOf(
+ COMPONENT_ENABLED_STATE_DEFAULT, COMPONENT_ENABLED_STATE_ENABLED);
+
+ try {
+ final List<ComponentEnabledSetting> enabledSettings = List.of(
+ new ComponentEnabledSetting(
+ ACTIVITY_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP),
+ new ComponentEnabledSetting(
+ SERVICE_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP));
+ setComponentEnabledSettingsAndWaitForBroadcasts(enabledSettings);
+ } finally {
+ final List<ComponentEnabledSetting> enabledSettings = List.of(
+ new ComponentEnabledSetting(ACTIVITY_COMPONENT, activityState, DONT_KILL_APP),
+ new ComponentEnabledSetting(SERVICE_COMPONENT, serviceState, DONT_KILL_APP));
+ setComponentEnabledSettingsAndWaitForBroadcasts(enabledSettings);
+ }
+ }
+
+ @Test
+ public void setComponentEnabledSettings_disableSelfAndStubApp_killStubApp()
+ throws Exception {
+ final int activityState = mPackageManager.getComponentEnabledSetting(ACTIVITY_COMPONENT);
+ final int stubState = mPackageManager.getComponentEnabledSetting(STUB_ACTIVITY_COMPONENT);
+ assertThat(activityState).isAnyOf(
+ COMPONENT_ENABLED_STATE_DEFAULT, COMPONENT_ENABLED_STATE_ENABLED);
+ assertThat(stubState).isAnyOf(
+ COMPONENT_ENABLED_STATE_DEFAULT, COMPONENT_ENABLED_STATE_ENABLED);
+
+ final Intent intent = new Intent();
+ intent.setComponent(STUB_SERVICE_COMPONENT);
+ final AtomicBoolean killed = new AtomicBoolean();
+ mServiceTestRule.bindService(intent, new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ killed.set(true);
+ }
+ }, Context.BIND_AUTO_CREATE);
+ mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
+ android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
+
+ try {
+ final List<ComponentEnabledSetting> enabledSettings = List.of(
+ new ComponentEnabledSetting(
+ ACTIVITY_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP),
+ new ComponentEnabledSetting(
+ STUB_ACTIVITY_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, 0));
+ setComponentEnabledSettingsAndWaitForBroadcasts(enabledSettings);
+ TestUtils.waitUntil("Waiting for the process " + STUB_PACKAGE_NAME
+ + " to die", () -> killed.get());
+ } finally {
+ final List<ComponentEnabledSetting> enabledSettings = List.of(
+ new ComponentEnabledSetting(ACTIVITY_COMPONENT, activityState, DONT_KILL_APP),
+ new ComponentEnabledSetting(STUB_ACTIVITY_COMPONENT, stubState, 0));
+ setComponentEnabledSettingsAndWaitForBroadcasts(enabledSettings);
+ mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
+ public void setComponentEnabledSettings_noStateChanged_noBroadcastReceived() {
+ final int activityState = mPackageManager.getComponentEnabledSetting(ACTIVITY_COMPONENT);
+ final int serviceState = mPackageManager.getComponentEnabledSetting(SERVICE_COMPONENT);
+ final List<ComponentEnabledSetting> enabledSettings = List.of(
+ new ComponentEnabledSetting(ACTIVITY_COMPONENT, activityState, DONT_KILL_APP),
+ new ComponentEnabledSetting(SERVICE_COMPONENT, serviceState, DONT_KILL_APP));
+
+ assertThrows(TimeoutException.class,
+ () -> setComponentEnabledSettingsAndWaitForBroadcasts(enabledSettings));
+ }
+
+ @Test
+ public void clearApplicationUserData_resetComponentEnabledSettings() throws Exception {
+ assertThat(installPackage(MOCK_LAUNCHER_APK)).isTrue();
+ final List<ComponentEnabledSetting> settings = List.of(
+ new ComponentEnabledSetting(RESET_ENABLED_SETTING_ACTIVITY_COMPONENT,
+ COMPONENT_ENABLED_STATE_ENABLED, 0 /* flags */),
+ new ComponentEnabledSetting(RESET_ENABLED_SETTING_RECEIVER_COMPONENT,
+ COMPONENT_ENABLED_STATE_ENABLED, 0 /* flags */),
+ new ComponentEnabledSetting(RESET_ENABLED_SETTING_SERVICE_COMPONENT,
+ COMPONENT_ENABLED_STATE_ENABLED, 0 /* flags */),
+ new ComponentEnabledSetting(RESET_ENABLED_SETTING_PROVIDER_COMPONENT,
+ COMPONENT_ENABLED_STATE_ENABLED, 0 /* flags */));
+
+ try {
+ mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
+ android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
+ // update component enabled settings
+ setComponentEnabledSettingsAndWaitForBroadcasts(settings);
+
+ clearApplicationUserData(MOCK_LAUNCHER_PACKAGE_NAME);
+
+ assertThat(mPackageManager
+ .getComponentEnabledSetting(RESET_ENABLED_SETTING_ACTIVITY_COMPONENT))
+ .isEqualTo(COMPONENT_ENABLED_STATE_DEFAULT);
+ assertThat(mPackageManager
+ .getComponentEnabledSetting(RESET_ENABLED_SETTING_RECEIVER_COMPONENT))
+ .isEqualTo(COMPONENT_ENABLED_STATE_DEFAULT);
+ assertThat(mPackageManager
+ .getComponentEnabledSetting(RESET_ENABLED_SETTING_SERVICE_COMPONENT))
+ .isEqualTo(COMPONENT_ENABLED_STATE_DEFAULT);
+ assertThat(mPackageManager
+ .getComponentEnabledSetting(RESET_ENABLED_SETTING_PROVIDER_COMPONENT))
+ .isEqualTo(COMPONENT_ENABLED_STATE_DEFAULT);
+ } finally {
+ mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+ }
+ }
+
+ private void setComponentEnabledSettingsAndWaitForBroadcasts(
+ List<ComponentEnabledSetting> enabledSettings)
+ throws InterruptedException, TimeoutException {
+ final List<ComponentName> componentsToWait = enabledSettings.stream()
+ .map(enabledSetting -> enabledSetting.getComponentName())
+ .collect(Collectors.toList());
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addDataScheme("package");
+ final CountDownLatch latch = new CountDownLatch(1 /* count */);
+ final BroadcastReceiver br = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String packageName = intent.getData() != null
+ ? intent.getData().getSchemeSpecificPart() : null;
+ final String[] receivedComponents = intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+ if (packageName == null || receivedComponents == null) {
+ return;
+ }
+ for (String componentString : receivedComponents) {
+ componentsToWait.remove(new ComponentName(packageName, componentString));
+ }
+ if (componentsToWait.isEmpty()) {
+ latch.countDown();
+ }
+ }
+ };
+ mContext.registerReceiver(br, filter);
+ try {
+ mPackageManager.setComponentEnabledSettings(enabledSettings);
+ if (!latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ throw new TimeoutException("Package changed broadcasts for " + componentsToWait
+ + " not received in " + TIMEOUT_MS + "ms");
+ }
+ for (ComponentEnabledSetting enabledSetting : enabledSettings) {
+ assertThat(mPackageManager.getComponentEnabledSetting(
+ enabledSetting.getComponentName()))
+ .isEqualTo(enabledSetting.getEnabledState());
+ }
+ } finally {
+ mContext.unregisterReceiver(br);
+ }
+ }
+
+ private void clearApplicationUserData(String packageName) {
+ final StringBuilder cmd = new StringBuilder("pm clear --user ");
+ cmd.append(UserHandle.myUserId()).append(" ");
+ cmd.append(packageName);
+ SystemUtil.runShellCommand(cmd.toString());
+ }
+
+ @Test
+ public void testPrebuiltSharedLibraries_existOnDevice() {
+ final List<SharedLibraryInfo> infos =
+ mPackageManager.getSharedLibraries(PackageManager.PackageInfoFlags.of(0)).stream()
+ .filter(info -> info.isBuiltin() && !info.isNative())
+ .collect(Collectors.toList());
+ assertThat(infos).isNotEmpty();
+
+ final List<SharedLibraryInfo> fileNotExistInfos = infos.stream()
+ .filter(info -> !(new File(info.getPath()).exists())).collect(
+ Collectors.toList());
+ assertThat(fileNotExistInfos).isEmpty();
+ }
+
+ @Test
+ public void testInstallUpdate_applicationIsKilled() throws Exception {
+ final Intent intent = new Intent();
+ intent.setComponent(STUB_SERVICE_COMPONENT);
+ final AtomicBoolean killed = new AtomicBoolean();
+ mServiceTestRule.bindService(intent, new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ killed.set(true);
+ }
+ }, Context.BIND_AUTO_CREATE);
+
+ installPackage(STUB_PACKAGE_APK);
+ // The application should be killed after updating.
+ TestUtils.waitUntil("Waiting for the process " + STUB_PACKAGE_NAME + " to die",
+ 10 /* timeoutSecond */, () -> killed.get());
+ }
+
+ @Test
+ public void testInstallUpdate_dontKill_applicationIsNotKilled() throws Exception {
+ final Intent intent = new Intent();
+ intent.setComponent(STUB_SERVICE_COMPONENT);
+ final AtomicBoolean killed = new AtomicBoolean();
+ mServiceTestRule.bindService(intent, new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ killed.set(true);
+ }
+ }, Context.BIND_AUTO_CREATE);
+
+ installPackage(STUB_PACKAGE_APK, true /* dontKill */);
+ // The application shouldn't be killed after updating with --dont-kill.
+ assertThrows(AssertionFailedError.class,
+ () -> TestUtils.waitUntil(
+ "Waiting for the process " + STUB_PACKAGE_NAME + " to die",
+ 10 /* timeoutSecond */, () -> killed.get()));
+ }
}
diff --git a/tests/tests/content/src/android/content/pm/cts/ProviderInfoListTest.java b/tests/tests/content/src/android/content/pm/cts/ProviderInfoListTest.java
index 2578058..1a6d78b 100644
--- a/tests/tests/content/src/android/content/pm/cts/ProviderInfoListTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ProviderInfoListTest.java
@@ -33,7 +33,8 @@
public void testApplicationInfoSquashed() throws Exception {
final PackageManager pm = getContext().getPackageManager();
final PackageInfo pi = pm.getPackageInfo(PACKAGE_NAME,
- PackageManager.GET_PROVIDERS | PackageManager.GET_UNINSTALLED_PACKAGES);
+ PackageManager.PackageInfoFlags.of(
+ PackageManager.GET_PROVIDERS | PackageManager.GET_UNINSTALLED_PACKAGES));
// Make sure the package contains more than 1 providers.
assertNotNull(pi);
diff --git a/tests/tests/content/src/android/content/pm/cts/ProviderInfoTest.java b/tests/tests/content/src/android/content/pm/cts/ProviderInfoTest.java
index b99e294..c3a0e83 100644
--- a/tests/tests/content/src/android/content/pm/cts/ProviderInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ProviderInfoTest.java
@@ -19,8 +19,8 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ProviderInfo;
import android.content.res.XmlResourceParser;
import android.os.Parcel;
import android.platform.test.annotations.AppModeFull;
@@ -42,8 +42,10 @@
// Test ProviderInfo()
new ProviderInfo();
// Test other methods
- ApplicationInfo appInfo = pm.getApplicationInfo(PACKAGE_NAME, 0);
- List<ProviderInfo> providers = pm.queryContentProviders(PACKAGE_NAME, appInfo.uid, 0);
+ ApplicationInfo appInfo = pm.getApplicationInfo(PACKAGE_NAME,
+ PackageManager.ApplicationInfoFlags.of(0));
+ List<ProviderInfo> providers = pm.queryContentProviders(PACKAGE_NAME, appInfo.uid,
+ PackageManager.ComponentInfoFlags.of(0));
Iterator<ProviderInfo> providerIterator = providers.iterator();
ProviderInfo current;
while (providerIterator.hasNext()) {
@@ -58,7 +60,7 @@
public void testProviderMetaData() {
final ProviderInfo info = getContext().getPackageManager()
.resolveContentProvider("android.content.cts.fileprovider",
- PackageManager.GET_META_DATA);
+ PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
final XmlResourceParser in = info.loadXmlMetaData(
getContext().getPackageManager(), "android.support.FILE_PROVIDER_PATHS");
try {
diff --git a/tests/tests/content/src/android/content/pm/cts/ResourcesHardeningTest.java b/tests/tests/content/src/android/content/pm/cts/ResourcesHardeningTest.java
index 5385577..1c2cbad 100644
--- a/tests/tests/content/src/android/content/pm/cts/ResourcesHardeningTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ResourcesHardeningTest.java
@@ -327,7 +327,7 @@
// Start the test app and indicate which test to run.
try (pidDetector; finishDetector) {
- final Intent launchIntent = new Intent(Intent.ACTION_VIEW);
+ final Intent launchIntent = new Intent(Intent.ACTION_MAIN);
launchIntent.setClassName(TestUtils.TEST_APP_PACKAGE, TestUtils.TEST_ACTIVITY_NAME);
launchIntent.putExtra(TestUtils.TEST_NAME_EXTRA_KEY, mTestName);
launchIntent.putExtra(TestUtils.TEST_ASSERT_SUCCESS_EXTRA_KEY, assertSuccess);
diff --git a/tests/tests/content/src/android/content/res/cts/AssetFileDescriptorTest.java b/tests/tests/content/src/android/content/res/cts/AssetFileDescriptorTest.java
index b617164..14580eb 100644
--- a/tests/tests/content/src/android/content/res/cts/AssetFileDescriptorTest.java
+++ b/tests/tests/content/src/android/content/res/cts/AssetFileDescriptorTest.java
@@ -16,13 +16,6 @@
package android.content.res.cts;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Arrays;
-
import android.content.res.AssetFileDescriptor;
import android.os.Bundle;
import android.os.Parcel;
@@ -30,6 +23,13 @@
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
public class AssetFileDescriptorTest extends AndroidTestCase {
private static final long START_OFFSET = 0;
private static final long LENGTH = 100;
@@ -114,12 +114,6 @@
} catch (IOException e) {
// expect
}
- try {
- mInputStream = mAssetFileDes.createInputStream();
- fail("Should throw IOException");
- } catch (IOException e) {
- // expect
- }
mAssetFileDes.close();
mAssetFileDes = null;
@@ -140,12 +134,6 @@
mInputStream.close();
mInputStream = null;
try {
- mInputStream = mAssetFileDes.createInputStream();
- fail("Should throw IOException");
- } catch (IOException e) {
- // expect
- }
- try {
mOutputStream = mAssetFileDes.createOutputStream();
fail("Should throw IOException");
} catch (IOException e) {
diff --git a/tests/tests/content/src/android/content/res/cts/AssetFileDescriptor_AutoCloseInputStreamTest.java b/tests/tests/content/src/android/content/res/cts/AssetFileDescriptor_AutoCloseInputStreamTest.java
index 58af714..106ee4e 100644
--- a/tests/tests/content/src/android/content/res/cts/AssetFileDescriptor_AutoCloseInputStreamTest.java
+++ b/tests/tests/content/src/android/content/res/cts/AssetFileDescriptor_AutoCloseInputStreamTest.java
@@ -152,6 +152,24 @@
assertEquals(FILE_DATA[2], mInput.read());
}
+ public void testTwoFileDescriptorsWorkIndependently() throws IOException {
+ openInput(0, FILE_LENGTH);
+
+ AssetFileDescriptor fd2 = new AssetFileDescriptor(mFd.getParcelFileDescriptor(),
+ 0,
+ FILE_LENGTH);
+ AssetFileDescriptor.AutoCloseInputStream input2 =
+ new AssetFileDescriptor.AutoCloseInputStream(fd2);
+
+ input2.skip(2);
+ input2.read();
+
+ for (int i = 0; i < FILE_LENGTH; i++) {
+ assertEquals(FILE_DATA[i], mInput.read());
+ }
+ assertEquals(FILE_END, mInput.read());
+ }
+
private void openInput(long startOffset, long length)
throws IOException {
if (mFd != null) {
diff --git a/tests/tests/content/src/android/content/res/cts/ConfigTest.java b/tests/tests/content/src/android/content/res/cts/ConfigTest.java
index e07164f..b61dfe8 100644
--- a/tests/tests/content/src/android/content/res/cts/ConfigTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ConfigTest.java
@@ -258,7 +258,8 @@
mContext = InstrumentationRegistry.getContext();
final PackageManager pm = mContext.getPackageManager();
try {
- ApplicationInfo appInfo = pm.getApplicationInfo(TEST_PACKAGE, 0);
+ ApplicationInfo appInfo = pm.getApplicationInfo(TEST_PACKAGE,
+ PackageManager.ApplicationInfoFlags.of(0));
mTargetSdkVersion = appInfo.targetSdkVersion;
} catch (NameNotFoundException e) {
fail("Should be able to find application info for this package");
@@ -704,13 +705,8 @@
@Test
public void testDensity() throws Exception {
- // have 32, 240 and the default 160 content.
- // rule is that closest wins, with down scaling (larger content)
- // being twice as nice as upscaling.
- // transition at H/2 * (-1 +/- sqrt(1+8L/H))
- // SO, X < 49 goes to 32
- // 49 >= X < 182 goes to 160
- // X >= 182 goes to 240
+ // Have 32, 240 and the default 160 content.
+ // Rule is that next highest wins.
TotalConfig config = makeClassicConfig();
config.setProperty(Properties.DENSITY, 2);
Resources res = config.getResources();
@@ -728,13 +724,6 @@
config = makeClassicConfig();
config.setProperty(Properties.DENSITY, 48);
res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple 32dpi");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag 32dpi"});
-
- config = makeClassicConfig();
- config.setProperty(Properties.DENSITY, 49);
- res = config.getResources();
checkValue(res, R.configVarying.simple, "simple default");
checkValue(res, R.configVarying.bag,
R.styleable.TestConfig, new String[]{"bag default"});
@@ -749,13 +738,6 @@
config = makeClassicConfig();
config.setProperty(Properties.DENSITY, 181);
res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple default");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag default"});
-
- config = makeClassicConfig();
- config.setProperty(Properties.DENSITY, 182);
- res = config.getResources();
checkValue(res, R.configVarying.simple, "simple 240dpi");
checkValue(res, R.configVarying.bag,
R.styleable.TestConfig, new String[]{"bag 240dpi"});
diff --git a/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java b/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
index bf43777..84a80dd 100644
--- a/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
@@ -181,6 +181,87 @@
assertEquals(1, cfg1.compareTo(cfg2));
}
+ public void testGenerateDiff() {
+ Configuration cfg1 = new Configuration();
+ Configuration cfg2 = new Configuration();
+
+ cfg1.fontScale = 2;
+ cfg2.fontScale = 3;
+
+ cfg1.mcc = 2;
+ cfg2.mcc = 3;
+
+ cfg1.mnc = 2;
+ cfg2.mnc = 3;
+
+ cfg1.locale = new Locale("1", "2", "3");
+ cfg1.locale = new Locale("3", "2", "1");
+
+ cfg1.touchscreen = 2;
+ cfg2.touchscreen = 3;
+
+ cfg1.keyboard = 2;
+ cfg2.keyboard = 3;
+
+ cfg1.keyboardHidden = 2;
+ cfg2.keyboardHidden = 3;
+
+ cfg1.navigation = 2;
+ cfg2.navigation = 3;
+
+ cfg1.navigationHidden = 3;
+ cfg2.navigationHidden = 2;
+
+ cfg1.orientation = 3;
+ cfg2.orientation = 2;
+
+ cfg1.screenLayout = Configuration.SCREENLAYOUT_SIZE_NORMAL
+ | Configuration.SCREENLAYOUT_LAYOUTDIR_RTL
+ | Configuration.SCREENLAYOUT_LONG_NO
+ | Configuration.SCREENLAYOUT_ROUND_YES;
+ cfg2.screenLayout = Configuration.SCREENLAYOUT_SIZE_LARGE
+ | Configuration.SCREENLAYOUT_LAYOUTDIR_LTR
+ | Configuration.SCREENLAYOUT_LONG_YES
+ | Configuration.SCREENLAYOUT_ROUND_NO;
+
+ cfg1.colorMode = Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES
+ | Configuration.COLOR_MODE_HDR_NO;
+ cfg2.colorMode = Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO
+ | Configuration.COLOR_MODE_HDR_YES;
+
+ cfg1.uiMode = Configuration.UI_MODE_TYPE_WATCH
+ | Configuration.UI_MODE_NIGHT_NO;
+ cfg2.uiMode = Configuration.UI_MODE_TYPE_NORMAL
+ | Configuration.UI_MODE_NIGHT_YES;
+
+ cfg1.screenWidthDp = 500;
+ cfg2.screenWidthDp = 600;
+
+ cfg1.screenHeightDp = 920;
+ cfg2.screenHeightDp = 900;
+
+ cfg1.smallestScreenWidthDp = 500;
+ cfg2.smallestScreenWidthDp = 600;
+
+ cfg1.densityDpi = 200;
+ cfg2.densityDpi = 220;
+
+ cfg1.assetsSeq = 4;
+ cfg2.assetsSeq = 5;
+
+ cfg1.fontWeightAdjustment = 2;
+ cfg1.fontWeightAdjustment = 3;
+
+ Configuration delta = Configuration.generateDelta(cfg1, cfg2);
+ assertEquals(cfg2, delta);
+
+ delta = Configuration.generateDelta(cfg2, cfg1);
+ assertEquals(cfg1, delta);
+
+ delta = Configuration.generateDelta(cfg1, cfg1);
+ assertEquals(new Configuration(), delta);
+ }
+
public void testDescribeContents() {
assertEquals(0, mConfigDefault.describeContents());
}
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 45b2216..99dfe58 100644
--- a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
@@ -1043,4 +1043,27 @@
Typeface typeface = mResources.getFont(R.font.sample_downloadable_font);
assertEquals(typeface, Typeface.create("sans-serif", Typeface.NORMAL));
}
+
+ public void testThemeCompare() {
+ Resources.Theme t1 = mResources.newTheme();
+ Resources.Theme t2 = mResources.newTheme();
+ assertTrue(t1.equals(t1));
+ assertTrue(t1.equals(t2));
+ assertTrue(t1.hashCode() == t2.hashCode());
+ assertFalse(t1.equals(null));
+ assertFalse(t1.equals(this));
+
+ t1.applyStyle(1, false);
+ assertFalse(t1.equals(t2));
+ assertFalse(t1.hashCode() == t2.hashCode());
+ t2.applyStyle(1, false);
+ assertTrue(t1.equals(t2));
+ assertTrue(t1.hashCode() == t2.hashCode());
+ t1.applyStyle(2, true);
+ assertFalse(t1.hashCode() == t2.hashCode());
+ assertFalse(t1.equals(t2));
+ t2.applyStyle(2, false);
+ assertFalse(t1.equals(t2));
+ assertFalse(t1.hashCode() == t2.hashCode());
+ }
}
diff --git a/tests/tests/cronet/Android.bp b/tests/tests/cronet/Android.bp
index 947d5c0..fc8ec7f 100644
--- a/tests/tests/cronet/Android.bp
+++ b/tests/tests/cronet/Android.bp
@@ -34,7 +34,7 @@
test_suites: [
"cts",
"general-tests",
- "mts",
+ "mts-cronet",
],
}
diff --git a/tests/tests/database/src/android/database/sqlite/cts/DatabaseStatementTest.java b/tests/tests/database/src/android/database/sqlite/cts/DatabaseStatementTest.java
index 247039a..31180c0 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/DatabaseStatementTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/DatabaseStatementTest.java
@@ -90,6 +90,19 @@
}
@MediumTest
+ public void testExecutePragmaStatement() {
+ SQLiteStatement statement = mDatabase.compileStatement("PRAGMA busy_timeout = 12000");
+ statement.execute();
+ statement.close();
+
+ // Assert connection has busy timeout configured
+ try (Cursor c = mDatabase.rawQuery("PRAGMA busy_timeout;", null)) {
+ assertTrue(c.moveToNext());
+ assertEquals(c.getInt(0), 12000);
+ }
+ }
+
+ @MediumTest
public void testSimpleQuery() throws Exception {
mDatabase.execSQL("CREATE TABLE test (num INTEGER NOT NULL, str TEXT NOT NULL);");
mDatabase.execSQL("INSERT INTO test VALUES (1234, 'hello');");
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 81d3657..935bd49 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
@@ -25,6 +25,7 @@
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.SQLException;
+import android.database.sqlite.SQLiteCantOpenDatabaseException;
import android.database.sqlite.SQLiteCursor;
import android.database.sqlite.SQLiteCursorDriver;
import android.database.sqlite.SQLiteDatabase;
@@ -135,6 +136,70 @@
db.close();
}
+ public void testOpenDatabase_fail_no_path() {
+ CursorFactory factory = MockSQLiteCursor::new;
+ SQLiteDatabase db = null;
+ try {
+ db = SQLiteDatabase.openDatabase("filename.db",
+ factory, SQLiteDatabase.CREATE_IF_NECESSARY);
+ } catch (SQLiteCantOpenDatabaseException e) {
+ assertTrue(
+ "Wrong exception message: " + e.getMessage(),
+ e.getMessage().contains("Directory not specified in the file path"));
+ assertFalse(
+ "Wrong exception message: " + e.getMessage(),
+ e.getMessage().contains("Unknown reason"));
+ } finally {
+ if (db != null) {
+ db.close();
+ }
+ }
+ }
+
+ public void testOpenDatabase_fail_root_path_create_if_necessary() {
+ CursorFactory factory = MockSQLiteCursor::new;
+ SQLiteDatabase db = null;
+ try {
+ db = SQLiteDatabase.openDatabase("/filename.db",
+ factory, SQLiteDatabase.CREATE_IF_NECESSARY);
+ } catch (SQLiteCantOpenDatabaseException e) {
+ assertTrue(
+ "Wrong exception message: " + e.getMessage(),
+ e.getMessage().contains(
+ "File /filename.db doesn't exist and CREATE_IF_NECESSARY is set"));
+ assertFalse(
+ "Wrong exception message: " + e.getMessage(),
+ e.getMessage().contains("Unknown reason"));
+ } finally {
+ if (db != null) {
+ db.close();
+ }
+ }
+ }
+
+ public void testOpenDatabase_fail_root_path_no_create() {
+ CursorFactory factory = MockSQLiteCursor::new;
+ SQLiteDatabase db = null;
+ try {
+ db = SQLiteDatabase.openDatabase("/filename.db",
+ factory, 0);
+ } catch (SQLiteCantOpenDatabaseException e) {
+ assertTrue(
+ "Wrong exception message: " + e.getMessage(),
+ e.getMessage().contains("File /filename.db doesn't exist"));
+ assertFalse(
+ "Wrong exception message: " + e.getMessage(),
+ e.getMessage().contains("CREATE_IF_NECESSARY"));
+ assertFalse(
+ "Wrong exception message: " + e.getMessage(),
+ e.getMessage().contains("Unknown reason"));
+ } finally {
+ if (db != null) {
+ db.close();
+ }
+ }
+ }
+
public void testDeleteDatabase() throws IOException {
File dbFile = new File(mDatabaseDir, "database_test12345678.db");
File journalFile = new File(dbFile.getPath() + "-journal");
@@ -560,6 +625,16 @@
}
}
+ public void testExecPerConnectionSQLPragma() {
+ mDatabase.execPerConnectionSQL("PRAGMA busy_timeout = 12000;", null);
+
+ // Assert connection has busy timeout configured
+ try (Cursor c = mDatabase.rawQuery("PRAGMA busy_timeout;", null)) {
+ assertTrue(c.moveToNext());
+ assertEquals(c.getInt(0), 12000);
+ }
+ }
+
public void testFindEditTable() {
String tables = "table1 table2 table3";
assertEquals("table1", SQLiteDatabase.findEditTable(tables));
diff --git a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java
index 6d77ebb..c378f48 100644
--- a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java
+++ b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java
@@ -66,6 +66,7 @@
// setters without write permission
trySetPropertyWithoutWritePermission(violations);
trySetPropertiesWithoutWritePermission(violations);
+ tryDeletePropertyWithoutWritePermission(violations);
// getters without read permission
tryGetPropertyWithoutReadPermission(violations);
@@ -120,6 +121,7 @@
// setters without write permission
trySetPropertyWithoutWritePermission(violations);
trySetPropertiesWithoutWritePermission(violations);
+ tryDeletePropertyWithoutWritePermission(violations);
// getters with read permission
tryGetPropertyWithReadPermission(violations);
@@ -278,6 +280,15 @@
}
}
+ private void tryDeletePropertyWithoutWritePermission(StringBuilder violations) {
+ try {
+ DeviceConfig.deleteProperty(NAMESPACE, KEY);
+ violations.append("DeviceConfig.deleteProperty() must not be accessible without "
+ + "WRITE_DEVICE_CONFIG permission.\n");
+ } catch (SecurityException e) {
+ }
+ }
+
private void tryGetPropertyWithoutReadPermission(StringBuilder violations) {
try {
DeviceConfig.getProperty(NAMESPACE, KEY);
diff --git a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java
index c1cbbad..cacaa29 100644
--- a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java
+++ b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import static org.junit.Assert.fail;
@@ -816,6 +817,106 @@
+ " getProperty() when property is not null", DEFAULT_FLOAT, result, 0.0f);
}
+ @Test
+ public void testDeleteProperty_nullNamespace() {
+ try {
+ DeviceConfig.deleteProperty(null, KEY1);
+ fail("DeviceConfig.deleteProperty() with null namespace must result in "
+ + "NullPointerException");
+ } catch (NullPointerException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDeleteProperty_nullName() {
+ try {
+ DeviceConfig.deleteProperty(NAMESPACE1, null);
+ fail("DeviceConfig.deleteProperty() with null name must result in "
+ + "NullPointerException");
+ } catch (NullPointerException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDeletePropertyString() {
+ setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, VALUE1);
+ deletePropertyAndAssertSuccessfulChange(NAMESPACE1, KEY1);
+ assertEquals("DeviceConfig.Properties.getString() must return default value if "
+ + "property is deleted", DEFAULT_VALUE,
+ DeviceConfig.getProperties(NAMESPACE1, KEY1).getString(KEY1, DEFAULT_VALUE));
+ }
+
+ @Test
+ public void testDeletePropertyBoolean() {
+ setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, String.valueOf(BOOLEAN_TRUE));
+ deletePropertyAndAssertSuccessfulChange(NAMESPACE1, KEY1);
+ assertEquals("DeviceConfig.Properties.getBoolean() must return default value if "
+ + "property is deleted", BOOLEAN_FALSE,
+ DeviceConfig.getProperties(NAMESPACE1, KEY1).getBoolean(KEY1,
+ DEFAULT_BOOLEAN_FALSE));
+ }
+
+ @Test
+ public void testDeletePropertyInt() {
+ setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, String.valueOf(VALID_INT));
+ deletePropertyAndAssertSuccessfulChange(NAMESPACE1, KEY1);
+ assertEquals("DeviceConfig.Properties.getInt() must return default value if "
+ + "property is deleted", DEFAULT_INT,
+ DeviceConfig.getProperties(NAMESPACE1, KEY1).getInt(KEY1, DEFAULT_INT));
+ }
+
+ @Test
+ public void testDeletePropertyLong() {
+ setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, String.valueOf(VALID_LONG));
+ deletePropertyAndAssertSuccessfulChange(NAMESPACE1, KEY1);
+ assertEquals("DeviceConfig.Properties.getLong() must return default value if "
+ + "property is deleted", DEFAULT_LONG,
+ DeviceConfig.getProperties(NAMESPACE1, KEY1).getLong(KEY1, DEFAULT_LONG));
+ }
+
+ @Test
+ public void testDeletePropertyFloat() {
+ setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, String.valueOf(VALID_FLOAT));
+ deletePropertyAndAssertSuccessfulChange(NAMESPACE1, KEY1);
+ assertEquals("DeviceConfig.Properties.getString() must return default value if "
+ + "property is deleted", DEFAULT_FLOAT,
+ DeviceConfig.getProperties(NAMESPACE1, KEY1).getFloat(KEY1, DEFAULT_FLOAT), 0.0f);
+ }
+
+ @Test
+ public void testDeleteProperty_withNonExistingProperty() {
+ assertNull(DeviceConfig.getProperty(NAMESPACE1, KEY1));
+ // Test that deletion returns true when the key doesn't exist
+ deletePropertyAndAssertSuccessfulChange(NAMESPACE1, KEY1);
+ }
+
+ @Test
+ public void testDeleteProperty_withUndeletedProperty() {
+ setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, VALUE1);
+ setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY2, VALUE2);
+ assertEquals(VALUE1, DeviceConfig.getProperty(NAMESPACE1, KEY1));
+ assertEquals(VALUE2, DeviceConfig.getProperty(NAMESPACE1, KEY2));
+ final Properties propertiesBeforeDeletion = DeviceConfig.getProperties(
+ NAMESPACE1, KEY1, KEY2);
+ assertEquals(VALUE1, propertiesBeforeDeletion.getString(KEY1, DEFAULT_VALUE));
+ assertEquals(VALUE2, propertiesBeforeDeletion.getString(KEY2, DEFAULT_VALUE));
+ // Only delete one property, leaving another one undeleted
+ final Properties propertiesAfterDeletion = deletePropertyAndAssertSuccessfulChange(
+ NAMESPACE1, KEY1);
+ final String result = DeviceConfig.getString(NAMESPACE1, KEY1, DEFAULT_VALUE);
+ assertEquals("DeviceConfig.getString() must return default value if property is "
+ + "deleted", DEFAULT_VALUE, result);
+ assertNull("DeviceConfig.getProperty() must return null if property is deleted",
+ DeviceConfig.getProperty(NAMESPACE1, KEY1));
+ assertEquals(VALUE2, DeviceConfig.getProperty(NAMESPACE1, KEY2));
+ assertEquals("DeviceConfig.Properties.getString() must return default value if "
+ + "property is deleted", DEFAULT_VALUE, propertiesAfterDeletion.getString(KEY1,
+ DEFAULT_VALUE));
+ assertEquals(VALUE2, propertiesBeforeDeletion.getString(KEY2, DEFAULT_VALUE));
+ }
+
/**
* Test that properties listener is successfully registered and provides callbacks on value
* change when DeviceConfig.setProperty is called.
@@ -837,6 +938,16 @@
}
/**
+ * Test that properties listener is successfully registered and provides callbacks on value
+ * change when DeviceConfig.deleteProperty is called.
+ */
+ @Test
+ public void testPropertiesListener_deleteProperty() {
+ setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, VALUE1);
+ deletePropertyAndAssertSuccessfulChange(NAMESPACE1, KEY1);
+ }
+
+ /**
* Test that two properties listeners subscribed to the same namespace are successfully
* registered and unregistered while receiving correct updates in all states.
*/
@@ -1082,6 +1193,26 @@
return propertiesUpdate.properties;
}
+ private Properties deletePropertyAndAssertSuccessfulChange(String namespace, String name) {
+ final List<PropertyUpdate> receivedUpdates = new ArrayList<>();
+ OnPropertiesChangedListener changeListener = createOnPropertiesChangedListener(receivedUpdates);
+
+ DeviceConfig.addOnPropertiesChangedListener(namespace, EXECUTOR, changeListener);
+
+ assertTrue(DeviceConfig.deleteProperty(namespace, name));
+ assertNull("DeviceConfig.getProperty() must return null if property is deleted",
+ DeviceConfig.getProperty(namespace, name));
+ waitForListenerUpdateOrTimeout(receivedUpdates, 1);
+ DeviceConfig.removeOnPropertiesChangedListener(changeListener);
+
+ assertEquals("Failed to receive update to OnPropertiesChangedListener",
+ receivedUpdates.size(), 1);
+ PropertyUpdate propertiesUpdate = receivedUpdates.get(0);
+ propertiesUpdate.assertEqual(namespace, name, null);
+
+ return propertiesUpdate.properties;
+ }
+
private void nullifyProperty(String namespace, String key) {
if (DeviceConfig.getString(namespace, key, null) != null) {
setPropertiesAndAssertSuccessfulChange(namespace, key, null);
diff --git a/tests/tests/display/src/android/display/cts/DefaultDisplayModeTest.java b/tests/tests/display/src/android/display/cts/DefaultDisplayModeTest.java
new file mode 100644
index 0000000..1d05b0f
--- /dev/null
+++ b/tests/tests/display/src/android/display/cts/DefaultDisplayModeTest.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.display.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.Manifest;
+import android.content.Context;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Display;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import com.android.compatibility.common.util.DisplayUtil;
+import com.android.compatibility.common.util.FeatureUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class DefaultDisplayModeTest {
+ private final static int DISPLAY_CHANGE_TIMEOUT_SECS = 3;
+
+ private DisplayManager mDisplayManager;
+ private Display mDefaultDisplay;
+ private Display.Mode mOriginalDisplayModeSettings;
+
+ @Rule
+ public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE,
+ Manifest.permission.HDMI_CEC);
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ assumeTrue("Need an Android TV device to run this test.", FeatureUtil.isTV());
+ assertTrue("Physical display is expected.", DisplayUtil.isDisplayConnected(context));
+
+ mDisplayManager = context.getSystemService(DisplayManager.class);
+ mDefaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ cacheOriginalUserPreferredModeSetting();
+ mDisplayManager.clearGlobalUserPreferredDisplayMode();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ restoreOriginalDisplayModeSettings();
+ }
+
+ @Test
+ public void testSetUserPreferredDisplayModeThrowsExceptionWithInvalidMode() {
+ assertThrows(
+ "The mode is invalid. Width, height and refresh rate should be positive.",
+ IllegalArgumentException.class,
+ () -> mDisplayManager.setGlobalUserPreferredDisplayMode(
+ new Display.Mode(-1, 1080, 120.0f)));
+
+ assertThrows(
+ "The mode is invalid. Width, height and refresh rate should be positive.",
+ IllegalArgumentException.class,
+ () -> mDisplayManager.setGlobalUserPreferredDisplayMode(
+ new Display.Mode(720, 1080, 0.0f)));
+ }
+
+ @Test
+ public void testDisplayChangedOnSetAndClearUserPreferredDisplayMode() throws Exception {
+ Display.Mode[] modes = mDefaultDisplay.getSupportedModes();
+ assumeTrue("Need two or more display modes to exercise switching.", modes.length > 1);
+
+ // Test set
+ Display.Mode initialDefaultMode = mDefaultDisplay.getDefaultMode();
+
+ Display.Mode newDefaultMode = findNonDefaultMode(mDefaultDisplay);
+ assertNotNull(newDefaultMode);
+ DefaultModeListener listener =
+ new DefaultModeListener(mDefaultDisplay, newDefaultMode.getModeId());
+ Handler handler = new Handler(Looper.getMainLooper());
+ mDisplayManager.registerDisplayListener(listener, handler);
+ try {
+ mDisplayManager.setGlobalUserPreferredDisplayMode(newDefaultMode);
+ assertTrue(listener.await());
+ } finally {
+ mDisplayManager.unregisterDisplayListener(listener);
+ }
+
+ // Test clear
+ listener = new DefaultModeListener(mDefaultDisplay, initialDefaultMode.getModeId());
+ mDisplayManager.registerDisplayListener(listener, handler);
+ try {
+ mDisplayManager.clearGlobalUserPreferredDisplayMode();
+ assertTrue(listener.await());
+ } finally {
+ mDisplayManager.unregisterDisplayListener(listener);
+ }
+ }
+
+ @Test
+ public void testDisplayChangedOnSetAndClearUserPreferredDisplayModeForSpecificDevice()
+ throws Exception {
+ Display.Mode[] modes = mDefaultDisplay.getSupportedModes();
+ assumeTrue("Need two or more display modes to exercise switching.", modes.length > 1);
+
+ // Test set
+ Display.Mode initialDefaultMode = mDefaultDisplay.getDefaultMode();
+
+ Display.Mode newDefaultMode = findNonDefaultMode(mDefaultDisplay);
+ assertNotNull(newDefaultMode);
+ DefaultModeListener listener =
+ new DefaultModeListener(mDefaultDisplay, newDefaultMode.getModeId());
+ Handler handler = new Handler(Looper.getMainLooper());
+ mDisplayManager.registerDisplayListener(listener, handler);
+ try {
+ mDefaultDisplay.setUserPreferredDisplayMode(newDefaultMode);
+ assertTrue(listener.await());
+ } finally {
+ mDisplayManager.unregisterDisplayListener(listener);
+ }
+
+ // Test clear
+ listener = new DefaultModeListener(mDefaultDisplay, initialDefaultMode.getModeId());
+ mDisplayManager.registerDisplayListener(listener, handler);
+ try {
+ mDefaultDisplay.clearUserPreferredDisplayMode();
+ assertTrue(listener.await());
+ } finally {
+ mDisplayManager.unregisterDisplayListener(listener);
+ }
+ }
+
+ @Test
+ public void testSetUserPreferredDisplayModeForSpecificDevice() {
+ Display.Mode[] modes = mDefaultDisplay.getSupportedModes();
+ assumeTrue("Need two or more display modes to exercise switching.", modes.length > 1);
+
+ // Set a display mode which is different from default display mode
+ Display.Mode newDefaultMode = findNonDefaultMode(mDefaultDisplay);
+ assertNotNull(newDefaultMode);
+ mDefaultDisplay.setUserPreferredDisplayMode(newDefaultMode);
+ assertTrue(mDefaultDisplay.getUserPreferredDisplayMode()
+ .matches(newDefaultMode.getPhysicalWidth(),
+ newDefaultMode.getPhysicalHeight(),
+ newDefaultMode.getRefreshRate()));
+
+ mDefaultDisplay.clearUserPreferredDisplayMode();
+ assertNull(mDefaultDisplay.getUserPreferredDisplayMode());
+ }
+
+ @Test
+ public void testSetUserPreferredRefreshRateForSpecificDevice() {
+ Display.Mode[] modes = mDefaultDisplay.getSupportedModes();
+ assumeTrue("Need two or more display modes to exercise switching.", modes.length > 1);
+
+ // Set a refresh rate which is different from default display refresh rate
+ float refreshRate = findNonDefaultRefreshRate(mDefaultDisplay);
+ assumeTrue("Need two or more refresh rates to exercise switching.", refreshRate != 0.0f);
+
+ mDefaultDisplay.setUserPreferredDisplayMode(
+ new Display.Mode.Builder().setRefreshRate(refreshRate).build());
+ assertNotNull(mDefaultDisplay.getUserPreferredDisplayMode());
+ assertEquals(
+ refreshRate,
+ mDefaultDisplay.getUserPreferredDisplayMode().getRefreshRate(),
+ 0.00001 /* delta */);
+
+ mDefaultDisplay.clearUserPreferredDisplayMode();
+ assertNull(mDefaultDisplay.getUserPreferredDisplayMode());
+ }
+
+ @Test
+ public void testSetUserPreferredResolutionForSpecificDevice() {
+ Display.Mode[] modes = mDefaultDisplay.getSupportedModes();
+ assumeTrue("Need two or more display modes to exercise switching.", modes.length > 1);
+
+ // Set a refresh rate which is different from default display refresh rate
+ Point resolution = findNonDefaultResolution(mDefaultDisplay);
+ assumeTrue("Need two or more resolutions to exercise switching.",
+ resolution.x != -1 && resolution.y != -1);
+
+ mDefaultDisplay.setUserPreferredDisplayMode(
+ new Display.Mode.Builder().setResolution(resolution.x, resolution.y).build());
+ assertNotNull(mDefaultDisplay.getUserPreferredDisplayMode());
+ assertEquals(resolution.x,
+ mDefaultDisplay.getUserPreferredDisplayMode().getPhysicalWidth());
+ assertEquals(resolution.y,
+ mDefaultDisplay.getUserPreferredDisplayMode().getPhysicalHeight());
+
+ mDefaultDisplay.clearUserPreferredDisplayMode();
+ assertNull(mDefaultDisplay.getUserPreferredDisplayMode());
+ }
+
+ @Test
+ public void testGetUserPreferredDisplayMode() {
+ Display.Mode[] modes = mDefaultDisplay.getSupportedModes();
+ assumeTrue("Need two or more display modes to exercise switching.", modes.length > 1);
+
+ // Set a display mode which is different from default display mode
+ Display.Mode newDefaultMode = findNonDefaultMode(mDefaultDisplay);
+ assertNotNull(newDefaultMode);
+ mDisplayManager.setGlobalUserPreferredDisplayMode(newDefaultMode);
+ assertTrue(mDisplayManager.getGlobalUserPreferredDisplayMode()
+ .matches(newDefaultMode.getPhysicalWidth(),
+ newDefaultMode.getPhysicalHeight(),
+ newDefaultMode.getRefreshRate()));
+
+ mDisplayManager.clearGlobalUserPreferredDisplayMode();
+ assertNull(mDisplayManager.getGlobalUserPreferredDisplayMode());
+ }
+
+ private void cacheOriginalUserPreferredModeSetting() {
+ mOriginalDisplayModeSettings =
+ mDisplayManager.getGlobalUserPreferredDisplayMode();
+ }
+
+ private void restoreOriginalDisplayModeSettings() {
+ // mDisplayManager can be null if the test assumptions if setUp have failed.
+ if (mDisplayManager == null) {
+ return;
+ }
+ if (mOriginalDisplayModeSettings == null) {
+ mDisplayManager.clearGlobalUserPreferredDisplayMode();
+ } else {
+ mDisplayManager.setGlobalUserPreferredDisplayMode(mOriginalDisplayModeSettings);
+ }
+ }
+
+ private Display.Mode findNonDefaultMode(Display display) {
+ for (Display.Mode mode : display.getSupportedModes()) {
+ if (mode.getModeId() != display.getDefaultMode().getModeId()) {
+ return mode;
+ }
+ }
+ return null;
+ }
+
+ private float findNonDefaultRefreshRate(Display display) {
+ for (Display.Mode mode : display.getSupportedModes()) {
+ if (mode.getRefreshRate() != display.getDefaultMode().getRefreshRate()) {
+ return mode.getRefreshRate();
+ }
+ }
+ return 0.0f;
+ }
+
+ private Point findNonDefaultResolution(Display display) {
+ for (Display.Mode mode : display.getSupportedModes()) {
+ if (mode.getPhysicalWidth() != display.getDefaultMode().getPhysicalWidth()
+ || mode.getPhysicalHeight() != display.getDefaultMode().getPhysicalHeight()) {
+ return new Point(mode.getPhysicalWidth(), mode.getPhysicalHeight());
+ }
+ }
+ return new Point(-1, -1);
+ }
+
+ private static class DefaultModeListener implements DisplayManager.DisplayListener {
+ private final Display mDisplay;
+ private final int mAwaitedDefaultModeId;
+ private final CountDownLatch mLatch;
+
+ private DefaultModeListener(Display display, int awaitedDefaultModeId) {
+ mDisplay = display;
+ mAwaitedDefaultModeId = awaitedDefaultModeId;
+ mLatch = new CountDownLatch(1);
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {}
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId != mDisplay.getDisplayId()) {
+ return;
+ }
+
+ if (mAwaitedDefaultModeId == mDisplay.getDefaultMode().getModeId()) {
+ mLatch.countDown();
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {}
+
+ public boolean await() throws InterruptedException {
+ return mLatch.await(DISPLAY_CHANGE_TIMEOUT_SECS, TimeUnit.SECONDS);
+ }
+ };
+}
diff --git a/tests/tests/dreams/Android.bp b/tests/tests/dreams/Android.bp
index 00fc883..acf751f 100644
--- a/tests/tests/dreams/Android.bp
+++ b/tests/tests/dreams/Android.bp
@@ -20,6 +20,7 @@
name: "CtsDreamsTestCases",
defaults: ["cts_defaults"],
static_libs: [
+ "compatibility-device-util-axt",
"ctstestrunner-axt",
"junit",
],
@@ -36,4 +37,7 @@
"cts",
"general-tests",
],
+ data: [
+ ":CtsDreamOverlayTestApp",
+ ],
}
diff --git a/tests/tests/dreams/AndroidTest.xml b/tests/tests/dreams/AndroidTest.xml
index f6bd05a..cc4c9b5 100644
--- a/tests/tests/dreams/AndroidTest.xml
+++ b/tests/tests/dreams/AndroidTest.xml
@@ -23,6 +23,7 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsDreamsTestCases.apk" />
+ <option name="test-file-name" value="CtsDreamOverlayTestApp.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.dreams.cts" />
diff --git a/tests/tests/dreams/CtsDreamOverlayTestApp/Android.bp b/tests/tests/dreams/CtsDreamOverlayTestApp/Android.bp
new file mode 100644
index 0000000..d905df0
--- /dev/null
+++ b/tests/tests/dreams/CtsDreamOverlayTestApp/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsDreamOverlayTestApp",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "current",
+ srcs: [
+ "src/**/*.java",
+ ],
+}
diff --git a/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml b/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..1dc1d07
--- /dev/null
+++ b/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.app.dream.cts.app">
+ <application android:label="CtsDreamTestApp">
+ <service
+ android:name=".DreamOverlayService"
+ android:exported="true">
+ </service>
+ <service
+ android:name=".TestDreamService"
+ android:exported="true"
+ android:permission="android.permission.BIND_DREAM_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.dreams.DreamService" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
diff --git a/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/DreamOverlayService.java b/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/DreamOverlayService.java
new file mode 100644
index 0000000..53c70b3
--- /dev/null
+++ b/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/DreamOverlayService.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.dream.cts.app;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+/**
+ * {@link DreamOverlayService} provides a test implementation of
+ * {@link android.service.dreams.DreamOverlayService}. When informed of the dream state, the service
+ * populates a child window with a simple view.Once that view's visibility changes, the dream
+ * broadcasts an action that tests wait upon as a signal the overlay has been displayed.
+ */
+public class DreamOverlayService extends android.service.dreams.DreamOverlayService {
+ public static final String ACTION_DREAM_OVERLAY_SHOWN =
+ "android.app.dream.cts.app.action.overlay_shown";
+ public static final String TEST_PACKAGE = "android.dreams.cts";
+
+ @Override
+ public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
+ addWindowOverlay(layoutParams);
+ }
+
+ private void addWindowOverlay(WindowManager.LayoutParams layoutParams) {
+ FrameLayout layout = new FrameLayout(this);
+ layout.setBackgroundColor(Color.YELLOW);
+ layout.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+
+ // Add a listener for when the root layout becomes visible. We use this event to signal the
+ // dream overlay has been shown.
+ layout.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+ if (layout.getVisibility() == View.VISIBLE) {
+ final Intent intent = new Intent();
+ intent.setPackage(TEST_PACKAGE);
+ intent.setAction(ACTION_DREAM_OVERLAY_SHOWN);
+ sendBroadcast(intent);
+ requestExit();
+ }
+ });
+
+ final WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+ wm.addView(layout, layoutParams);
+ }
+}
diff --git a/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/TestDreamService.java b/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/TestDreamService.java
new file mode 100644
index 0000000..0508b00
--- /dev/null
+++ b/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/TestDreamService.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.dream.cts.app;
+
+import android.graphics.Color;
+import android.service.dreams.DreamService;
+import android.widget.FrameLayout;
+
+/**
+ * {@link TestDreamService} is a minimal concrete {@link DreamService} implementation that sets
+ * the entire window to be blue.
+ */
+public class TestDreamService extends DreamService {
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ setInteractive(false);
+ setFullscreen(true);
+
+ final FrameLayout frameLayout = new FrameLayout(getApplicationContext());
+ frameLayout.setBackgroundColor(Color.BLUE);
+ setContentView(frameLayout);
+ }
+}
diff --git a/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java b/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java
new file mode 100644
index 0000000..be82ebf
--- /dev/null
+++ b/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.dreams.cts;
+
+import android.Manifest;
+import android.app.DreamManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.ServiceManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DreamOverlayTest {
+ private static final String DREAM_OVERLAY_SERVICE_COMPONENT =
+ "android.app.dream.cts.app/.DreamOverlayService";
+ private static final String DREAM_SERVICE_COMPONENT =
+ "android.app.dream.cts.app/.TestDreamService";
+ private static final String ACTION_DREAM_OVERLAY_SHOWN =
+ "android.app.dream.cts.app.action.overlay_shown";
+
+ private static final int TIMEOUT_SECONDS = 5;
+
+ private static final ComponentName DREAM_COMPONENT_NAME = ComponentName.unflattenFromString(
+ DREAM_SERVICE_COMPONENT);
+
+ private DreamManager mDreamManager;
+ /**
+ * A simple {@link BroadcastReceiver} implementation that counts down a
+ * {@link CountDownLatch} when a matching message is received
+ */
+ static final class OverlayVisibilityReceiver extends BroadcastReceiver {
+ final CountDownLatch mLatch;
+
+ OverlayVisibilityReceiver(CountDownLatch latch) {
+ mLatch = latch;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mLatch.countDown();
+ }
+ }
+
+ @Rule
+ public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ Manifest.permission.INTERACT_ACROSS_USERS, Manifest.permission.WRITE_DREAM_STATE);
+
+ @Before
+ public void setup() throws ServiceManager.ServiceNotFoundException {
+ mDreamManager = new DreamManager(InstrumentationRegistry.getTargetContext());
+
+ mDreamManager.setActiveDream(DREAM_COMPONENT_NAME);
+
+ // Register the overlay service.
+ mDreamManager.setDreamOverlay(ComponentName.unflattenFromString(
+ DREAM_OVERLAY_SERVICE_COMPONENT));
+ }
+
+ @After
+ public void teardown() {
+ mDreamManager.setActiveDream(null);
+
+ // Unregister overlay service.
+ mDreamManager.setDreamOverlay(null);
+ }
+
+ @Test
+ public void testDreamOverlayAppearance() throws Exception {
+ // Listen for the overlay to be shown
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ InstrumentationRegistry.getTargetContext().registerReceiver(
+ new OverlayVisibilityReceiver(countDownLatch),
+ new IntentFilter(ACTION_DREAM_OVERLAY_SHOWN));
+
+ mDreamManager.startDream(DREAM_COMPONENT_NAME);
+
+ // Wait on count down latch.
+ assert (countDownLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS));
+ }
+}
diff --git a/tests/tests/gamemanager/AndroidManifest.xml b/tests/tests/gamemanager/AndroidManifest.xml
index ca874b9..434f73d 100644
--- a/tests/tests/gamemanager/AndroidManifest.xml
+++ b/tests/tests/gamemanager/AndroidManifest.xml
@@ -20,11 +20,16 @@
<application android:appCategory="game">
<uses-library android:name="android.test.runner"/>
+ <meta-data android:name="com.android.app.gamemode.performance.enabled"
+ android:value="true"/>
+ <meta-data android:name="com.android.app.gamemode.battery.enabled"
+ android:value="true"/>
<activity android:name=".GameManagerCtsActivity"
android:label="GameManagerCtsActivity"
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.FRAMEWORK_INSTRUMENTATION_TEST"/>
</intent-filter>
</activity>
diff --git a/tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerTest.java b/tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerTest.java
index 2ad0f22..1afdc7e 100644
--- a/tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerTest.java
+++ b/tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerTest.java
@@ -16,35 +16,45 @@
package android.gamemanager.cts;
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.GameManager;
-import android.content.Context;
-import android.util.Log;
-
-import androidx.test.ext.junit.rules.ActivityScenarioRule;
-
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.GameManager;
+import android.app.GameState;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.support.test.uiautomator.UiDevice;
+
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.compatibility.common.util.SystemUtil;
-import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
public class GameManagerTest {
private static final String TAG = "GameManagerTest";
+ private static final String GAME_OVERLAY_FEATURE_NAME =
+ "com.google.android.feature.GAME_OVERLAY";
+ private static final String POWER_DUMPSYS_CMD = "dumpsys android.hardware.power.IPower/default";
+ private static final Pattern GAME_LOADING_REGEX =
+ Pattern.compile("^GAME_LOADING\\t(\\d*)\\t\\d*$", Pattern.MULTILINE);
private GameManagerCtsActivity mActivity;
private Context mContext;
private GameManager mGameManager;
+ private UiDevice mUiDevice;
@Rule
public ActivityScenarioRule<GameManagerCtsActivity> mActivityRule =
@@ -56,8 +66,10 @@
mActivity = activity;
});
- mContext = getInstrumentation().getContext();
+ final Instrumentation instrumentation = getInstrumentation();
+ mContext = instrumentation.getContext();
mGameManager = mContext.getSystemService(GameManager.class);
+ mUiDevice = UiDevice.getInstance(instrumentation);
}
/**
@@ -66,6 +78,9 @@
*/
@Test
public void testGetGameModeUnsupported() {
+ assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+ .hasSystemFeature(GAME_OVERLAY_FEATURE_NAME));
+
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mGameManager,
(gameManager) -> gameManager.setGameMode(mActivity.getPackageName(),
GameManager.GAME_MODE_UNSUPPORTED));
@@ -82,6 +97,9 @@
*/
@Test
public void testGetGameModeStandard() {
+ assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+ .hasSystemFeature(GAME_OVERLAY_FEATURE_NAME));
+
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mGameManager,
(gameManager) -> gameManager.setGameMode(mActivity.getPackageName(),
GameManager.GAME_MODE_STANDARD));
@@ -98,6 +116,9 @@
*/
@Test
public void testGetGameModePerformance() {
+ assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+ .hasSystemFeature(GAME_OVERLAY_FEATURE_NAME));
+
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mGameManager,
(gameManager) -> gameManager.setGameMode(mActivity.getPackageName(),
GameManager.GAME_MODE_PERFORMANCE));
@@ -114,6 +135,9 @@
*/
@Test
public void testGetGameModeBattery() {
+ assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+ .hasSystemFeature(GAME_OVERLAY_FEATURE_NAME));
+
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mGameManager,
(gameManager) -> gameManager.setGameMode(mActivity.getPackageName(),
GameManager.GAME_MODE_BATTERY));
@@ -123,4 +147,43 @@
Assert.assertEquals("Game Manager returned incorrect value.",
GameManager.GAME_MODE_BATTERY, gameMode);
}
+
+ private int getGameLoadingCount() throws IOException {
+ final Matcher matcher =
+ GAME_LOADING_REGEX.matcher(mUiDevice.executeShellCommand(POWER_DUMPSYS_CMD));
+ assumeTrue(matcher.find());
+ return Integer.parseInt(matcher.group(1));
+ }
+
+ /**
+ * Test that GameManager::setGameContext() with an 'isLoading' context does not invokes the mode
+ * on the PowerHAL when performance mode is not invoked.
+ */
+ @Test
+ public void testSetGameContextStandardMode() throws IOException, InterruptedException {
+ final int gameLoadingCountBefore = getGameLoadingCount();
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mGameManager,
+ (gameManager) -> gameManager.setGameMode(mActivity.getPackageName(),
+ GameManager.GAME_MODE_STANDARD));
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mGameManager, (gameManager) ->
+ gameManager.setGameState(new GameState(true, GameState.MODE_NONE)));
+ Thread.sleep(500); // Wait for change to take effect.
+ Assert.assertEquals(gameLoadingCountBefore, getGameLoadingCount());
+ }
+
+ /**
+ * Test that GameManager::setGameContext() with an 'isLoading' context actually invokes the mode
+ * on the PowerHAL when performance mode is invoked.
+ */
+ @Test
+ public void testSetGameContextPerformanceMode() throws IOException, InterruptedException {
+ final int gameLoadingCountBefore = getGameLoadingCount();
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mGameManager,
+ (gameManager) -> gameManager.setGameMode(mActivity.getPackageName(),
+ GameManager.GAME_MODE_PERFORMANCE));
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mGameManager, (gameManager) ->
+ gameManager.setGameState(new GameState(true, GameState.MODE_NONE)));
+ Thread.sleep(500); // Wait for change to take effect.
+ Assert.assertEquals(gameLoadingCountBefore + 1, getGameLoadingCount());
+ }
}
diff --git a/tests/tests/graphics/assets/fonts/layout/hyphenation.ttx b/tests/tests/graphics/assets/fonts/layout/hyphenation.ttx
index 20ca0e7..0badb7c 100644
--- a/tests/tests/graphics/assets/fonts/layout/hyphenation.ttx
+++ b/tests/tests/graphics/assets/fonts/layout/hyphenation.ttx
@@ -157,6 +157,66 @@
<map code="0x0079" name="1em" /> <!-- y -->
<map code="0x007A" name="1em" /> <!-- z -->
+ <map code="0X00ED" name="1em" /> <!-- á -->
+ <map code="0X00ED" name="1em" /> <!-- í -->
+
+ <map code="0X00F6" name="1em" /> <!-- ö -->
+
+ <map code="0X0101" name="1em" /> <!-- ā -->
+
+ <map code="0X016B" name="1em" /> <!-- ū -->
+
+ <map code="0X03B1" name="1em" /> <!-- α -->
+ <map code="0X03B3" name="1em" /> <!-- γ -->
+ <map code="0X03B5" name="1em" /> <!-- ε -->
+ <map code="0X03B6" name="1em" /> <!-- ζ -->
+ <map code="0X03BC" name="1em" /> <!-- μ -->
+ <map code="0X03BD" name="1em" /> <!-- ν -->
+ <map code="0X03BF" name="1em" /> <!-- ο -->
+ <map code="0X03C1" name="1em" /> <!-- ρ -->
+ <map code="0X03C2" name="1em" /> <!-- ς -->
+ <map code="0X03C5" name="1em" /> <!-- υ -->
+ <map code="0X03CC" name="1em" /> <!-- ό -->
+
+ <map code="0X0432" name="1em" /> <!-- в -->
+ <map code="0X043A" name="1em" /> <!-- к -->
+ <map code="0X043C" name="1em" /> <!-- м -->
+ <map code="0X043D" name="1em" /> <!-- н -->
+ <map code="0X0442" name="1em" /> <!-- т -->
+ <map code="0X044C" name="1em" /> <!-- ь -->
+ <map code="0X0456" name="1em" /> <!-- і -->
+
+ <map code="0X0423" name="1em" /> <!-- У -->
+ <map code="0X0432" name="1em" /> <!-- в -->
+ <map code="0X0435" name="1em" /> <!-- е -->
+ <map code="0X0437" name="1em" /> <!-- з -->
+ <map code="0X043A" name="1em" /> <!-- к -->
+ <map code="0X043C" name="1em" /> <!-- м -->
+ <map code="0X043D" name="1em" /> <!-- н -->
+ <map code="0X043E" name="1em" /> <!-- о -->
+ <map code="0X0441" name="1em" /> <!-- с -->
+ <map code="0X0442" name="1em" /> <!-- т -->
+ <map code="0X044C" name="1em" /> <!-- ь -->
+ <map code="0X0456" name="1em" /> <!-- і -->
+
+ <map code="0X10D0" name="1em" /> <!-- ა -->
+ <map code="0X10D7" name="1em" /> <!-- თ -->
+ <map code="0X10D8" name="1em" /> <!-- ი -->
+ <map code="0X10DB" name="1em" /> <!-- მ -->
+ <map code="0X10DC" name="1em" /> <!-- ნ -->
+ <map code="0X10DD" name="1em" /> <!-- ო -->
+ <map code="0X10E0" name="1em" /> <!-- რ -->
+ <map code="0X10E4" name="1em" /> <!-- ფ -->
+ <map code="0X10EA" name="1em" /> <!-- ც -->
+
+ <map code="0x1218" name="1em" /> <!-- መ -->
+ <map code="0x1260" name="1em" /> <!-- በ -->
+ <map code="0x1278" name="1em" /> <!-- ቸ -->
+ <map code="0x1295" name="1em" /> <!-- ን -->
+ <map code="0x12CD" name="1em" /> <!-- ው -->
+ <map code="0x12DB" name="1em" /> <!-- ዛ -->
+ <map code="0x130B" name="1em" /> <!-- ጋ -->
+
<map code="0x2010" name="2em" /> <!-- HYPHEN -->
<map code="0x058A" name="3em" /> <!-- ARMENIAN HYPHEN -->
<map code="0x05BE" name="4em" /> <!-- MAQAF -->
diff --git a/tests/tests/graphics/assets/fonts/layout/linebreak.ttf b/tests/tests/graphics/assets/fonts/layout/linebreak.ttf
index eb18c0a..2638ae0 100644
--- a/tests/tests/graphics/assets/fonts/layout/linebreak.ttf
+++ b/tests/tests/graphics/assets/fonts/layout/linebreak.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/layout/linebreak.ttx b/tests/tests/graphics/assets/fonts/layout/linebreak.ttx
index 18a82a9..f8759b9 100644
--- a/tests/tests/graphics/assets/fonts/layout/linebreak.ttx
+++ b/tests/tests/graphics/assets/fonts/layout/linebreak.ttx
@@ -179,6 +179,7 @@
<map code="0x0078" name="1em" /> <!-- x -->
<map code="0x0079" name="1em" /> <!-- y -->
<map code="0x007A" name="1em" /> <!-- z -->
+ <map code="0x2010" name="1em" /> <!-- hyphen -->
</cmap_format_4>
</cmap>
diff --git a/tests/tests/graphics/jni/VulkanTestHelpers.cpp b/tests/tests/graphics/jni/VulkanTestHelpers.cpp
index ea16aea..89049e0 100644
--- a/tests/tests/graphics/jni/VulkanTestHelpers.cpp
+++ b/tests/tests/graphics/jni/VulkanTestHelpers.cpp
@@ -121,6 +121,18 @@
ASSERT(status == VK_SUCCESS || status == VK_INCOMPLETE);
ASSERT(gpuCount > 0);
+ VkPhysicalDeviceSamplerYcbcrConversionFeaturesKHR ycbcrFeatures = {
+ .sType =
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES_KHR,
+ .pNext = nullptr,
+ };
+ VkPhysicalDeviceFeatures2KHR physicalDeviceFeatures = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR,
+ .pNext = &ycbcrFeatures,
+ };
+ vkGetPhysicalDeviceFeatures2(mGpu, &physicalDeviceFeatures);
+ ASSERT(ycbcrFeatures.samplerYcbcrConversion == VK_TRUE);
+
VkPhysicalDeviceProperties physicalDeviceProperties;
vkGetPhysicalDeviceProperties(mGpu, &physicalDeviceProperties);
std::vector<const char *> deviceExt;
@@ -182,7 +194,7 @@
VkDeviceCreateInfo deviceCreateInfo{
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
- .pNext = nullptr,
+ .pNext = &ycbcrFeatures,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &queueCreateInfo,
.enabledLayerCount = 0,
@@ -226,18 +238,6 @@
(PFN_vkImportSemaphoreFdKHR)vkGetDeviceProcAddr(mDevice, "vkImportSemaphoreFdKHR");
ASSERT(mPfnImportSemaphoreFd);
- VkPhysicalDeviceSamplerYcbcrConversionFeaturesKHR ycbcrFeatures{
- .sType =
- VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES_KHR,
- .pNext = nullptr,
- };
- VkPhysicalDeviceFeatures2KHR physicalDeviceFeatures{
- .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR,
- .pNext = &ycbcrFeatures,
- };
- vkGetPhysicalDeviceFeatures2(mGpu, &physicalDeviceFeatures);
- ASSERT(ycbcrFeatures.samplerYcbcrConversion == VK_TRUE);
-
vkGetDeviceQueue(mDevice, 0, 0, &mQueue);
vkGetPhysicalDeviceMemoryProperties(mGpu, &mMemoryProperties);
diff --git a/tests/tests/graphics/res/color/colorPrimaryDark_csl_with_theme_attr.xml b/tests/tests/graphics/res/color/colorPrimaryDark_csl_with_theme_attr.xml
new file mode 100644
index 0000000..28f3b20
--- /dev/null
+++ b/tests/tests/graphics/res/color/colorPrimaryDark_csl_with_theme_attr.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="?attr/colorPrimaryDark"/>
+</selector>
\ No newline at end of file
diff --git a/tests/tests/graphics/res/color/colorPrimary_csl_with_theme_attr.xml b/tests/tests/graphics/res/color/colorPrimary_csl_with_theme_attr.xml
new file mode 100644
index 0000000..cccd6dd
--- /dev/null
+++ b/tests/tests/graphics/res/color/colorPrimary_csl_with_theme_attr.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="?attr/colorPrimary"/>
+</selector>
\ No newline at end of file
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 2663f3c..7c51385 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/adaptiveicondrawable.xml b/tests/tests/graphics/res/drawable/adaptiveicondrawable.xml
new file mode 100644
index 0000000..1ffe9d5
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/adaptiveicondrawable.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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="@android:color/white"/>
+ <foreground android:drawable="@android:color/black"/>
+ <monochrome android:drawable="@android:color/system_accent2_800"/>
+</adaptive-icon>
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_color_all_theme.xml b/tests/tests/graphics/res/drawable/gradientdrawable_color_all_theme.xml
new file mode 100644
index 0000000..75cdb82
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_color_all_theme.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <gradient
+ android:startColor="@color/colorPrimary_csl_with_theme_attr"
+ android:centerColor="@color/colorPrimaryDark_csl_with_theme_attr"
+ android:endColor="@color/colorPrimaryDark_csl_with_theme_attr"/>
+</shape>
\ No newline at end of file
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_color_mix_theme.xml b/tests/tests/graphics/res/drawable/gradientdrawable_color_mix_theme.xml
new file mode 100644
index 0000000..db2d2fc
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_color_mix_theme.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <gradient
+ android:startColor="?attr/colorAccent"
+ android:centerColor="@color/colorPrimary_csl_with_theme_attr"
+ android:endColor="?attr/colorPrimaryDark"/>
+</shape>
\ No newline at end of file
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_noCenterColor_all_theme.xml b/tests/tests/graphics/res/drawable/gradientdrawable_noCenterColor_all_theme.xml
new file mode 100644
index 0000000..63115ae
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_noCenterColor_all_theme.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <gradient
+ android:startColor="@color/colorPrimary_csl_with_theme_attr"
+ android:endColor="@color/colorPrimaryDark_csl_with_theme_attr"/>
+</shape>
\ No newline at end of file
diff --git a/tests/tests/graphics/res/xml/valid_themes.xml b/tests/tests/graphics/res/xml/valid_themes.xml
new file mode 100644
index 0000000..f693be1
--- /dev/null
+++ b/tests/tests/graphics/res/xml/valid_themes.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<themes>
+ <theme color="ffb9567a">
+ <spritz>ffffff,fffbfa,faeeef,ebe0e1,cfc4c5,b3a8aa,988f90,7d7475,655c5e,4c4546,352f30,201a1c,000000,ffffff,fffbfa,faeeef,ebe0e1,cfc4c5,b3a8aa,988f90,7d7475,655c5e,4c4546,352f30,201a1c,000000,ffffff,fffbfa,faeeef,ebe0e1,cfc4c5,b3a8aa,988f90,7d7475,655c5e,4c4546,352f30,201a1c,000000,ffffff,fffbfa,faeeef,ebe0e1,cfc4c5,b3a8aa,988f90,7d7475,655c5e,4c4546,352f30,201a1c,000000,ffffff,fffbfa,faeeef,ebe0e1,cfc4c5,b3a8aa,988f90,7d7475,655c5e,4c4546,352f30,201a1c,000000</spritz>
+ <tonal_spot>ffffff,fffbfa,ffecf1,ffd9e4,ffb0ca,e992ae,cb7793,ac5d78,904660,733049,581932,3d031e,000000,ffffff,fffbfa,ffecf1,ffd9e2,e2bdc6,c5a2ab,a98891,8d6e76,74565f,5b3f47,422a31,2b151c,000000,ffffff,fffbfa,ffeddf,ffdcbf,efbc94,d1a27a,b48762,976d4a,7c5635,623f20,48290b,2f1500,000000,ffffff,fffbfa,faeeef,ebe0e1,cfc4c5,b3a8aa,988f90,7d7475,655c5e,4c4546,352f30,201a1c,000000,ffffff,fffbfa,ffecf1,f2dee2,d5c2c6,b9a6aa,9e8c90,827276,6a5a5e,514347,392d30,23191b,000000</tonal_spot>
+ <vibrant>ffffff,fffbfa,ffecf1,ffd9e4,ffb0ca,e992ae,cb7793,ac5d78,904660,733049,581932,3d031e,000000,ffffff,fffbfa,ffedef,ffdadf,f3b7bf,d59ca3,b88389,9a696f,805158,653b41,4b252b,321017,000000,ffffff,fffbfa,ffecea,ffdad7,ffb3b1,ef918f,d17776,b15e5c,944747,763031,591a1c,3d050a,000000,ffffff,fffbfa,ffecf1,f2dee2,d5c2c6,b9a6aa,9e8c90,827276,6a5a5e,514347,392d30,23191b,000000,ffffff,fffbfa,ffecf1,ffd9e2,e2bdc6,c5a2ab,a98891,8d6e76,74565f,5b3f47,422a31,2b151c,000000</vibrant>
+ <expressive>ffffff,fffbfb,ffeaff,f9d7ff,e7b4f9,ca9adc,ae80c0,9265a4,784e8a,5f3670,471f58,2f0641,000000,ffffff,fffbfa,ffecea,ffdad7,f5b7b5,d79d9a,ba8281,9c6867,815150,663b3a,4c2524,331111,000000,ffffff,fefbff,efefff,dde0ff,bac3ff,9da6ec,838cd0,6872b3,50599a,384180,212a68,081253,000000,ffffff,fffbfa,ffecf1,ffd9e2,e2bdc6,c5a2ab,a98891,8d6e76,74565f,5b3f47,422a31,2b151c,000000,ffffff,fffbfa,ffecf1,ffd9e4,fcb2c8,de98ad,c07e92,a26378,874c60,6b3648,511f32,370b1d,000000</expressive>
+ </theme>
+ <theme color="ffb16307">
+ <spritz>ffffff,fffbfa,faeee8,ece0da,cfc4be,b3a9a3,988f89,7d746f,655d58,4c4641,362f2b,201a16,000000,ffffff,fffbfa,faeee8,ece0da,cfc4be,b3a9a3,988f89,7d746f,655d58,4c4641,362f2b,201a16,000000,ffffff,fffbfa,faeee8,ece0da,cfc4be,b3a9a3,988f89,7d746f,655d58,4c4641,362f2b,201a16,000000,ffffff,fffbfa,faeee8,ece0da,cfc4be,b3a9a3,988f89,7d746f,655d58,4c4641,362f2b,201a16,000000,ffffff,fffbfa,faeee8,ece0da,cfc4be,b3a9a3,988f89,7d746f,655d58,4c4641,362f2b,201a16,000000</spritz>
+ <tonal_spot>ffffff,fffbfa,ffeddf,ffdcbf,ffb776,e59b57,c78140,a76729,8b5011,6d3900,4d2700,2f1500,000000,ffffff,fffbfa,ffeddf,ffdcc1,e2c0a5,c5a58b,aa8b72,8d705a,745943,5a422d,412c19,2a1707,000000,ffffff,fdffd7,eef5be,e0e8b1,c4cb97,a8af7e,8e9565,727a4d,5b6238,444a22,2d330e,191e00,000000,ffffff,fffbfa,faeee8,ece0da,cfc4be,b3a9a3,988f89,7d746f,655d58,4c4641,362f2b,201a16,000000,ffffff,fffbfa,ffede0,f3dfd1,d5c3b6,baa89c,9e8e82,837368,6a5b51,51443b,3a2e26,231a12,000000</tonal_spot>
+ <vibrant>ffffff,fffbfa,ffeddf,ffdcbf,ffb776,e59b57,c78140,a76729,8b5011,6d3900,4d2700,2f1500,000000,ffffff,fffbf9,ffeedc,ffddb6,e9bf8f,cca476,b08a5e,937046,795831,5e411b,442b07,2b1700,000000,ffffff,fffbf9,ffeed5,ffdea8,f3be61,d4a349,b78932,9a6e18,7e5700,604100,432c00,281900,000000,ffffff,fffbfa,ffede0,f3dfd1,d5c3b6,baa89c,9e8e82,837368,6a5b51,51443b,3a2e26,231a12,000000,ffffff,fffbfa,ffeddf,ffdcc1,e2c0a5,c5a58b,aa8b72,8d705a,745943,5a422d,412c19,2a1707,000000</vibrant>
+ <expressive>ffffff,fffbfa,ffecea,ffdad7,ffb3b1,f0918f,d17876,b05e5d,944747,763031,591a1c,3d050a,000000,ffffff,fffbf9,ffeed5,ffdea8,e3c28c,c6a774,aa8c5c,8d7243,745a2f,5a4319,412d05,281900,000000,ffffff,fffbfa,ffebf7,ffd7f2,fab0e5,dc95c8,bf7bad,a16191,864978,6b325f,511b47,380330,000000,ffffff,fffbfa,ffeddf,ffdcc1,e2c0a5,c5a58b,aa8b72,8d705a,745943,5a422d,412c19,2a1707,000000,ffffff,fffbfa,ffeddf,ffdcbf,fab982,dc9e6a,be8452,9f6a3a,845326,683c10,4d2700,2f1500,000000</expressive>
+ </theme>
+ <theme color="ff6e7e0f">
+ <spritz>ffffff,fffcf4,f3f1e8,e5e3da,c8c7bf,adaba4,929189,777670,5f5f58,474741,30312b,1c1c17,000000,ffffff,fffcf4,f3f1e8,e5e3da,c8c7bf,adaba4,929189,777670,5f5f58,474741,30312b,1c1c17,000000,ffffff,fffcf4,f3f1e8,e5e3da,c8c7bf,adaba4,929189,777670,5f5f58,474741,30312b,1c1c17,000000,ffffff,fffcf4,f3f1e8,e5e3da,c8c7bf,adaba4,929189,777670,5f5f58,474741,30312b,1c1c17,000000,ffffff,fffcf4,f3f1e8,e5e3da,c8c7bf,adaba4,929189,777670,5f5f58,474741,30312b,1c1c17,000000</spritz>
+ <tonal_spot>ffffff,fdffd7,eaf99a,dbeb8d,c0ce75,a4b35c,8a9844,707c2d,586416,404c00,2b3400,181e00,000000,ffffff,fcffdc,f0f3d0,e2e5c2,c6c9a8,aaae8e,909375,74795b,5c6145,454930,2f321b,1a1d08,000000,ffffff,f0fffa,cbfbed,bdeddf,a1d0c3,87b4a8,6d9a8e,527e73,3b665c,224e45,05372f,002019,000000,ffffff,fffcf4,f3f1e8,e5e3da,c8c7bf,adaba4,929189,777670,5f5f58,474741,30312b,1c1c17,000000,ffffff,fefdec,f2f2e0,e4e4d3,c7c7b7,acac9d,919283,767769,5e6052,46483b,303126,1b1c12,000000</tonal_spot>
+ <vibrant>ffffff,fdffd7,eaf99a,dbeb8d,c0ce75,a4b35c,8a9844,707c2d,586416,404c00,2b3400,181e00,000000,ffffff,f9ffe2,e6f7c5,d8e9b8,bbcd9d,a1b184,86976b,6c7c52,54633d,3d4b27,273513,131f02,000000,ffffff,f6ffe8,ccffb3,bff0a6,a3d48c,89b873,6f9c5b,568143,3e692e,275018,103803,022100,000000,ffffff,fefdec,f2f2e0,e4e4d3,c7c7b7,acac9d,919283,767769,5e6052,46483b,303126,1b1c12,000000,ffffff,fcffdc,f0f3d0,e2e5c2,c6c9a8,aaae8e,909375,74795b,5c6145,454930,2f321b,1a1d08,000000</vibrant>
+ <expressive>ffffff,fffbf9,ffeed5,ffdea8,f3be61,d5a349,b78832,996e18,7e5700,604100,432c00,281900,000000,ffffff,f6ffe8,def9cd,d0eabf,b5cea5,99b38b,7f9872,657d59,4e6543,374d2d,213618,0c2006,000000,ffffff,fffbfa,ffede6,ffdbcd,ffb597,ef9471,cf7a59,af6141,924a2c,753318,571e04,380d00,000000,ffffff,fcffdc,f0f3d0,e2e5c2,c6c9a8,aaae8e,909375,74795b,5c6145,454930,2f321b,1a1d08,000000,ffffff,fcffd4,ecf8ad,dee99f,c1cd86,a7b16d,8c9656,717b3e,5a6328,424b12,2c3400,181e00,000000</expressive>
+ </theme>
+ <theme color="ff008772">
+ <spritz>ffffff,fafdfa,eff1ef,e0e3e0,c4c7c5,a8aca9,8e918f,737775,5c5f5d,444846,2e3130,191c1b,000000,ffffff,fafdfa,eff1ef,e0e3e0,c4c7c5,a8aca9,8e918f,737775,5c5f5d,444846,2e3130,191c1b,000000,ffffff,fafdfa,eff1ef,e0e3e0,c4c7c5,a8aca9,8e918f,737775,5c5f5d,444846,2e3130,191c1b,000000,ffffff,fafdfa,eff1ef,e0e3e0,c4c7c5,a8aca9,8e918f,737775,5c5f5d,444846,2e3130,191c1b,000000,ffffff,fafdfa,eff1ef,e0e3e0,c4c7c5,a8aca9,8e918f,737775,5c5f5d,444846,2e3130,191c1b,000000</spritz>
+ <tonal_spot>ffffff,f0fffa,b3ffec,96f4dc,79d8c0,5cbca5,3fa08b,188571,006b59,005143,00382d,002019,000000,ffffff,f0fffa,dbf7ed,cde9df,b1ccc3,96b1a8,7d968e,627b74,4b635c,334b45,1c352e,06201a,000000,ffffff,fafcff,e4f3ff,c6e7ff,aacae3,8fafc8,7594ac,5a7a90,426278,2a4a5f,103447,001e2f,000000,ffffff,fafdfa,eff1ef,e0e3e0,c4c7c5,a8aca9,8e918f,737775,5c5f5d,444846,2e3130,191c1b,000000,ffffff,f4fefa,e9f3ef,dae5e0,bec9c4,a3ada9,88938f,6e7875,57615d,3f4945,29322f,141d1b,000000</tonal_spot>
+ <vibrant>ffffff,f0fffa,b3ffec,96f4dc,79d8c0,5cbca5,3fa08b,188571,006b59,005143,00382d,002019,000000,ffffff,effffd,cafbf4,bbece5,a0d0c9,86b4ae,6b9994,517e79,396661,1f4e4a,013733,00201d,000000,ffffff,efffff,acffff,8ff2f5,73d6d9,55babd,339fa2,008386,00696c,004f51,003739,002021,000000,ffffff,f4fefa,e9f3ef,dae5e0,bec9c4,a3ada9,88938f,6e7875,57615d,3f4945,29322f,141d1b,000000,ffffff,f0fffa,dbf7ed,cde9df,b1ccc3,96b1a8,7d968e,627b74,4b635c,334b45,1c352e,06201a,000000</vibrant>
+ <expressive>ffffff,f6ffe8,ceffb2,c0f0a5,a5d38b,8ab872,709c5a,568142,40682d,285017,123802,042100,000000,ffffff,efffff,c9fafa,bcebec,a0cfd0,85b4b4,6b9999,507e7e,386667,1e4e4f,003738,002021,000000,ffffff,fffbf7,fff1b1,fae27c,dcc764,bfab4c,a49034,87751a,6e5e00,544600,3a3000,221b00,000000,ffffff,f0fffa,dbf7ed,cde9df,b1ccc3,96b1a8,7d968e,627b74,4b635c,334b45,1c352e,06201a,000000,ffffff,f0fffa,b9ffeb,abf0dd,8fd4c1,74b8a6,599d8c,3e8272,23695b,005143,00382d,002019,000000</expressive>
+ </theme>
+ <theme color="ff007fb6">
+ <spritz>ffffff,fbfcff,f0f0f3,e2e2e5,c6c6c9,aaabae,909194,757679,5c5e61,454749,2f3032,191c1e,000000,ffffff,fbfcff,f0f0f3,e2e2e5,c6c6c9,aaabae,909194,757679,5c5e61,454749,2f3032,191c1e,000000,ffffff,fbfcff,f0f0f3,e2e2e5,c6c6c9,aaabae,909194,757679,5c5e61,454749,2f3032,191c1e,000000,ffffff,fbfcff,f0f0f3,e2e2e5,c6c6c9,aaabae,909194,757679,5c5e61,454749,2f3032,191c1e,000000,ffffff,fbfcff,f0f0f3,e2e2e5,c6c6c9,aaabae,909194,757679,5c5e61,454749,2f3032,191c1e,000000</spritz>
+ <tonal_spot>ffffff,fafcff,e4f2ff,c7e6ff,8bcefd,70b2e1,5497c5,357ca8,10648f,004b6f,00344f,001e30,000000,ffffff,fafcff,e4f2ff,d3e5f5,b7c9d8,9caebd,8193a1,677887,50606e,384956,22323f,0c1d29,000000,ffffff,fffbfd,f7edff,ebdcff,cfc0e9,b2a5cc,978bb0,7c7095,64597b,4c4163,352b4a,201634,000000,ffffff,fbfcff,f0f0f3,e2e2e5,c6c6c9,aaabae,909194,757679,5c5e61,454749,2f3032,191c1e,000000,ffffff,fafcff,ecf1f9,dee3ea,c2c7ce,a6acb2,8b9198,70777d,595f65,41474d,2b3137,171c21,000000</tonal_spot>
+ <vibrant>ffffff,fafcff,e4f2ff,c7e6ff,8bcefd,70b2e1,5497c5,357ca8,10648f,004b6f,00344f,001e30,000000,ffffff,fcfcff,e8f1ff,cfe5ff,b0c9e7,95aecb,7b93af,607894,48607b,304962,19324b,001d34,000000,ffffff,fdfcff,ebf1ff,d4e3ff,a6c8ff,87adea,6c92cd,5177b1,375f97,1b477d,003063,001b3d,000000,ffffff,fafcff,ecf1f9,dee3ea,c2c7ce,a6acb2,8b9198,70777d,595f65,41474d,2b3137,171c21,000000,ffffff,fafcff,e4f2ff,d3e5f5,b7c9d8,9caebd,8193a1,677887,50606e,384956,22323f,0c1d29,000000</vibrant>
+ <expressive>ffffff,efffff,b4fdff,8ff3f7,72d6da,54babf,339fa3,008388,00696e,004f53,00373a,002022,000000,ffffff,fdfcff,ebf1ff,d4e3ff,b5c7ea,9aaccc,8092b1,657795,4d5f7c,364764,1f314c,071c36,000000,ffffff,f2fff4,c0ffd9,a4f3c4,88d7a9,6dbb8f,539f76,36845c,196b46,005230,003920,002110,000000,ffffff,fafcff,e4f2ff,d3e5f5,b7c9d8,9caebd,8193a1,677887,50606e,384956,22323f,0c1d29,000000,ffffff,fafcff,e4f2ff,c7e6ff,9dccf0,81b0d4,6796b8,4c7b9c,326383,154b6a,00344f,001e30,000000</expressive>
+ </theme>
+ <theme color="ff8267c2">
+ <spritz>ffffff,fffbfd,f5eff4,e6e1e5,c9c5c9,aeaaae,939094,787579,605d62,484649,313033,1c1b1e,000000,ffffff,fffbfd,f5eff4,e6e1e5,c9c5c9,aeaaae,939094,787579,605d62,484649,313033,1c1b1e,000000,ffffff,fffbfd,f5eff4,e6e1e5,c9c5c9,aeaaae,939094,787579,605d62,484649,313033,1c1b1e,000000,ffffff,fffbfd,f5eff4,e6e1e5,c9c5c9,aeaaae,939094,787579,605d62,484649,313033,1c1b1e,000000,ffffff,fffbfd,f5eff4,e6e1e5,c9c5c9,aeaaae,939094,787579,605d62,484649,313033,1c1b1e,000000</spritz>
+ <tonal_spot>ffffff,fffbfd,f7edff,ebddff,d2bcff,b5a0e8,9a86cc,7f6bae,675395,4e3b7c,372463,220b4e,000000,ffffff,fffbfd,f7edff,e9def8,ccc3dc,b1a7c0,958da4,7a7389,625b71,4a4358,332d41,1e182b,000000,ffffff,fffbfa,ffecf0,ffd8e3,f0b7c7,d29dab,b68391,986976,7e525f,633b48,4a2531,31101c,000000,ffffff,fffbfd,f5eff4,e6e1e5,c9c5c9,aeaaae,939094,787579,605d62,484649,313033,1c1b1e,000000,ffffff,fffbfd,f5eefa,e7e0eb,cbc4cf,afa9b4,948f99,79747e,615c66,49454f,322f37,1d1a22,000000</tonal_spot>
+ <vibrant>ffffff,fffbfd,f7edff,ebddff,d2bcff,b5a0e8,9a86cc,7f6bae,675395,4e3b7c,372463,220b4e,000000,ffffff,fffbfb,faecff,f1daff,d4bfe6,b8a4ca,9d89ae,816f92,695779,504060,392a48,241532,000000,ffffff,fffbfb,ffeaff,fad8ff,e8b4f9,cb99dc,af7fc0,9265a3,794e8a,5f3670,471e58,2f0641,000000,ffffff,fffbfd,f5eefa,e7e0eb,cbc4cf,afa9b4,948f99,79747e,615c66,49454f,322f37,1d1a22,000000,ffffff,fffbfd,f7edff,e9def8,ccc3dc,b1a7c0,958da4,7a7389,625b71,4a4358,332d41,1e182b,000000</vibrant>
+ <expressive>ffffff,fdfcff,ebf1ff,d3e3ff,a4c8ff,86ade9,6b92cd,5077b1,375f97,1a477e,003062,001b3c,000000,ffffff,fffbfb,ffeaff,f8d8ff,dbbce2,bea2c5,a388aa,876d8f,6e5675,553e5d,3e2846,27132f,000000,ffffff,f7fdff,d9f5ff,afecff,79d2ee,5cb7d1,3d9cb6,118199,00677d,004e5f,003542,001f28,000000,ffffff,fffbfd,f7edff,e9def8,ccc3dc,b1a7c0,958da4,7a7389,625b71,4a4358,332d41,1e182b,000000,ffffff,fffbfd,f7edff,ebddff,cfbef6,b4a2d9,9988be,7d6ea1,655788,4d3f6e,362856,211240,000000</expressive>
+ </theme>
+</themes>
\ No newline at end of file
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapShaderTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapShaderTest.java
index 5b3dedf..2c2ff6d 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapShaderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapShaderTest.java
@@ -22,12 +22,15 @@
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ColorUtils;
+
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -162,4 +165,47 @@
Assert.assertArrayEquals(new int[] { Color.RED, Color.BLUE, Color.BLUE, Color.RED },
pixels);
}
+
+ @Test
+ public void testFiltering() {
+ Bitmap bitmap = Bitmap.createBitmap(2, 2, Config.ARGB_8888);
+ bitmap.setPixel(0, 0, Color.RED);
+ bitmap.setPixel(1, 0, Color.BLUE);
+ bitmap.setPixel(0, 1, Color.BLUE);
+ bitmap.setPixel(1, 1, Color.RED);
+
+ BitmapShader shader = new BitmapShader(bitmap,
+ Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+ Assert.assertEquals(BitmapShader.FILTER_MODE_DEFAULT, shader.getFilterMode());
+
+ // use slightly left than half to avoid any confusion on which pixel
+ // is sampled with FILTER_MODE_NEAREST
+ Matrix matrix = new Matrix();
+ matrix.postScale(0.49f, 0.49f);
+ shader.setLocalMatrix(matrix);
+
+ Bitmap dstBitmap = Bitmap.createBitmap(1, 1, Config.ARGB_8888);
+ Canvas canvas = new Canvas(dstBitmap);
+ Paint paint = new Paint();
+ paint.setShader(shader);
+
+ paint.setFilterBitmap(false);
+ canvas.drawPaint(paint);
+ ColorUtils.verifyColor("expected solid red color", Color.RED, dstBitmap.getPixel(0, 0), 0);
+
+ paint.setFilterBitmap(true);
+ canvas.drawPaint(paint);
+ ColorUtils.verifyColor("color should be a blue/red mix", Color.valueOf(0.5f, 0.0f, 0.5f),
+ dstBitmap.getColor(0, 0), 0.05f);
+
+ shader.setFilterMode(BitmapShader.FILTER_MODE_NEAREST);
+ canvas.drawPaint(paint);
+ ColorUtils.verifyColor("expected solid red color", Color.RED, dstBitmap.getPixel(0, 0), 0);
+
+ shader.setFilterMode(BitmapShader.FILTER_MODE_LINEAR);
+ paint.setFilterBitmap(false);
+ canvas.drawPaint(paint);
+ ColorUtils.verifyColor("color should be a blue/red mix", Color.valueOf(0.5f, 0.0f, 0.5f),
+ dstBitmap.getColor(0, 0), 0.05f);
+ }
}
diff --git a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
index 13a549d..8e3ebdd 100644
--- a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
+++ b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
@@ -19,6 +19,8 @@
import static android.system.OsConstants.EINVAL;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import android.app.Activity;
@@ -44,7 +46,6 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -56,19 +57,25 @@
System.loadLibrary("ctsgraphics_jni");
}
- private static String TAG = "FrameRateCtsActivity";
+ private static final String TAG = "FrameRateCtsActivity";
private static final long FRAME_RATE_SWITCH_GRACE_PERIOD_SECONDS = 2;
private static final long STABLE_FRAME_RATE_WAIT_SECONDS = 1;
private static final long POST_BUFFER_INTERVAL_MILLIS = 500;
private static final int PRECONDITION_WAIT_MAX_ATTEMPTS = 5;
private static final long PRECONDITION_WAIT_TIMEOUT_SECONDS = 20;
private static final long PRECONDITION_VIOLATION_WAIT_TIMEOUT_SECONDS = 3;
- private static final float FRAME_RATE_TOLERANCE = 0.01f;
+ private static final float FRAME_RATE_TOLERANCE_STRICT = 0.01f;
+
+ // Tolerance which doesn't differentiate between the fractional refresh rate pairs, e.g.
+ // 59.94 and 60 will be considered the same refresh rate.
+ // Use this tolerance to verify the refresh rate after calling setFrameRate with
+ // {@Surface.FRAME_RATE_COMPATIBILITY_DEFAULT}.
+ private static final float FRAME_RATE_TOLERANCE_RELAXED = 0.1f;
private DisplayManager mDisplayManager;
private SurfaceView mSurfaceView;
private Handler mHandler = new Handler(Looper.getMainLooper());
- private Object mLock = new Object();
+ private final Object mLock = new Object();
private Surface mSurface = null;
private float mDeviceFrameRate;
private ModeChangedEvents mModeChangedEvents = new ModeChangedEvents();
@@ -193,23 +200,20 @@
mColor = color;
if (mApi == Api.SURFACE || mApi == Api.ANATIVE_WINDOW || mApi == Api.SURFACE_CONTROL) {
- assertTrue("No parent surface", parentSurfaceControl != null);
+ assertNotNull("No parent surface", parentSurfaceControl);
mSurfaceControl = new SurfaceControl.Builder()
.setParent(parentSurfaceControl)
.setName(mName)
.setBufferSize(destFrame.right - destFrame.left,
destFrame.bottom - destFrame.top)
.build();
- SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
- try {
+ try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
transaction.setGeometry(mSurfaceControl, null, destFrame, Surface.ROTATION_0)
.apply();
- } finally {
- transaction.close();
}
mSurface = new Surface(mSurfaceControl);
} else if (mApi == Api.NATIVE_SURFACE_CONTROL) {
- assertTrue("No parent surface", parentSurface != null);
+ assertNotNull("No parent surface", parentSurface);
mNativeSurfaceControl = nativeSurfaceControlCreate(parentSurface, mName,
destFrame.left, destFrame.top, destFrame.right, destFrame.bottom);
assertTrue("Failed to create a native SurfaceControl", mNativeSurfaceControl != 0);
@@ -231,14 +235,11 @@
rc = nativeWindowSetFrameRate(mSurface, frameRate, compatibility,
changeFrameRateStrategy);
} else if (mApi == Api.SURFACE_CONTROL) {
- SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
- try {
+ try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
transaction
- .setFrameRate(mSurfaceControl, frameRate, compatibility,
- changeFrameRateStrategy)
- .apply();
- } finally {
- transaction.close();
+ .setFrameRate(mSurfaceControl, frameRate, compatibility,
+ changeFrameRateStrategy)
+ .apply();
}
} else if (mApi == Api.NATIVE_SURFACE_CONTROL) {
nativeSurfaceControlSetFrameRate(mNativeSurfaceControl, frameRate, compatibility,
@@ -262,9 +263,8 @@
} else {
int rc = setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
if (mApi == Api.ANATIVE_WINDOW) {
- assertTrue("Expected -EINVAL return value from invalid call to"
- + " ANativeWindow_setFrameRate()",
- rc == -EINVAL);
+ assertEquals("Expected -EINVAL return value from invalid call to"
+ + " ANativeWindow_setFrameRate()", rc, -EINVAL);
}
}
}
@@ -274,11 +274,8 @@
String.format("Setting visibility for %s: %s", mName,
visible ? "visible" : "hidden"));
if (mApi == Api.SURFACE || mApi == Api.ANATIVE_WINDOW || mApi == Api.SURFACE_CONTROL) {
- SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
- try {
+ try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
transaction.setVisibility(mSurfaceControl, visible).apply();
- } finally {
- transaction.close();
}
} else if (mApi == Api.NATIVE_SURFACE_CONTROL) {
nativeSurfaceControlSetVisibility(mNativeSurfaceControl, visible);
@@ -309,11 +306,8 @@
mSurface = null;
}
if (mSurfaceControl != null) {
- SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
- try {
+ try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
transaction.reparent(mSurfaceControl, null).apply();
- } finally {
- transaction.close();
}
mSurfaceControl.release();
mSurfaceControl = null;
@@ -407,7 +401,7 @@
for (float frameRate : frameRates) {
if (uniqueFrameRates.isEmpty()
|| frameRate - uniqueFrameRates.get(uniqueFrameRates.size() - 1)
- >= FRAME_RATE_TOLERANCE) {
+ >= FRAME_RATE_TOLERANCE_STRICT) {
uniqueFrameRates.add(frameRate);
}
}
@@ -430,12 +424,12 @@
&& mode1.getPhysicalWidth() == mode2.getPhysicalWidth();
}
- private boolean isFrameRateMultiple(float higherFrameRate, float lowerFrameRate) {
+ private boolean isFrameRateMultiple(
+ float higherFrameRate, float lowerFrameRate, float tolerance) {
float multiple = higherFrameRate / lowerFrameRate;
int roundedMultiple = Math.round(multiple);
return roundedMultiple > 0
- && Math.abs(roundedMultiple * lowerFrameRate - higherFrameRate)
- <= FRAME_RATE_TOLERANCE;
+ && Math.abs(roundedMultiple * lowerFrameRate - higherFrameRate) <= tolerance;
}
// Returns two device-supported frame rates that aren't multiples of each other, or null if no
@@ -446,7 +440,8 @@
for (int i = 0; i < frameRates.size(); i++) {
for (int j = i + 1; j < frameRates.size(); j++) {
if (!isFrameRateMultiple(Math.max(frameRates.get(i), frameRates.get(j)),
- Math.min(frameRates.get(i), frameRates.get(j)))) {
+ Math.min(frameRates.get(i), frameRates.get(j)),
+ FRAME_RATE_TOLERANCE_RELAXED)) {
return new float[] {frameRates.get(i), frameRates.get(j)};
}
}
@@ -456,8 +451,8 @@
// Waits until our SurfaceHolder has a surface and the activity is resumed.
private void waitForPreconditions() throws InterruptedException {
- assertTrue(
- "Activity was unexpectedly destroyed", mActivityState != ActivityState.DESTROYED);
+ assertNotSame("Activity was unexpectedly destroyed", mActivityState,
+ ActivityState.DESTROYED);
if (mSurface == null || mActivityState != ActivityState.RUNNING) {
Log.i(TAG,
String.format(
@@ -473,8 +468,8 @@
mSurface != null, mActivityState == ActivityState.RUNNING),
timeRemainingMillis > 0);
mLock.wait(timeRemainingMillis);
- assertTrue("Activity was unexpectedly destroyed",
- mActivityState != ActivityState.DESTROYED);
+ assertNotSame("Activity was unexpectedly destroyed", mActivityState,
+ ActivityState.DESTROYED);
nowNanos = System.nanoTime();
}
// Make sure any previous mode changes are completed.
@@ -483,8 +478,8 @@
// Returns true if we encounter a precondition violation, false otherwise.
private boolean waitForPreconditionViolation() throws InterruptedException {
- assertTrue(
- "Activity was unexpectedly destroyed", mActivityState != ActivityState.DESTROYED);
+ assertNotSame("Activity was unexpectedly destroyed", mActivityState,
+ ActivityState.DESTROYED);
long nowNanos = System.nanoTime();
long endTimeNanos = nowNanos + PRECONDITION_VIOLATION_WAIT_TIMEOUT_SECONDS * 1_000_000_000L;
while (mSurface != null && mActivityState == ActivityState.RUNNING) {
@@ -493,8 +488,8 @@
break;
}
mLock.wait(timeRemainingMillis);
- assertTrue("Activity was unexpectedly destroyed",
- mActivityState != ActivityState.DESTROYED);
+ assertNotSame("Activity was unexpectedly destroyed", mActivityState,
+ ActivityState.DESTROYED);
nowNanos = System.nanoTime();
}
return mSurface == null || mActivityState != ActivityState.RUNNING;
@@ -507,7 +502,7 @@
}
// Returns true if we reached waitUntilNanos, false if some other event occurred.
- private boolean waitForEvents(long waitUntilNanos, List<TestSurface> surfaces)
+ private boolean waitForEvents(long waitUntilNanos, TestSurface[] surfaces)
throws InterruptedException {
int numModeChangedEvents = mModeChangedEvents.size();
long nowNanos = System.nanoTime();
@@ -539,21 +534,21 @@
return true;
}
- private void waitForStableFrameRate() throws InterruptedException {
- verifyCompatibleAndStableFrameRate(0, new ArrayList<>());
+ private void waitForStableFrameRate(TestSurface... surfaces) throws InterruptedException {
+ verifyCompatibleAndStableFrameRate(0, FRAME_RATE_TOLERANCE_STRICT, surfaces);
}
// Set expectedFrameRate to 0.0 to verify only stable frame rate.
- private void verifyCompatibleAndStableFrameRate(float expectedFrameRate,
- List<TestSurface> surfaces) throws InterruptedException {
+ private void verifyCompatibleAndStableFrameRate(float expectedFrameRate, float tolerance,
+ TestSurface... surfaces) throws InterruptedException {
Log.i(TAG, "Verifying compatible and stable frame rate");
long nowNanos = System.nanoTime();
long gracePeriodEndTimeNanos =
nowNanos + FRAME_RATE_SWITCH_GRACE_PERIOD_SECONDS * 1_000_000_000L;
while (true) {
- if (expectedFrameRate > FRAME_RATE_TOLERANCE) { // expectedFrameRate > 0
+ if (expectedFrameRate > tolerance) { // expectedFrameRate > 0
// Wait until we switch to a compatible frame rate.
- while (!isFrameRateMultiple(mDeviceFrameRate, expectedFrameRate)
+ while (!isFrameRateMultiple(mDeviceFrameRate, expectedFrameRate, tolerance)
&& !waitForEvents(gracePeriodEndTimeNanos, surfaces)) {
// Empty
}
@@ -698,7 +693,8 @@
int initialNumEvents = mModeChangedEvents.size();
surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
- verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
+ verifyCompatibleAndStableFrameRate(frameRate, FRAME_RATE_TOLERANCE_RELAXED,
+ surface);
verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
verifyModeSwitchesDontChangeResolution(initialNumEvents,
mModeChangedEvents.size());
@@ -707,7 +703,7 @@
surface.setFrameRate(0.f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
// Wait for potential mode switches
- verifyCompatibleAndStableFrameRate(0, Arrays.asList(surface));
+ waitForStableFrameRate(surface);
currentMode = display.getMode();
// Seamed rates should never generate a seamed switch.
@@ -727,7 +723,8 @@
int initialNumEvents = mModeChangedEvents.size();
surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
Surface.CHANGE_FRAME_RATE_ALWAYS);
- verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
+ verifyCompatibleAndStableFrameRate(frameRate, FRAME_RATE_TOLERANCE_RELAXED,
+ surface);
verifyModeSwitchesDontChangeResolution(initialNumEvents,
mModeChangedEvents.size());
}
@@ -781,14 +778,11 @@
surfaceB.setFrameRate(frameRateB, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
changeFrameRateStrategy);
- ArrayList<TestSurface> surfaces = new ArrayList<>();
- surfaces.add(surfaceA);
- surfaces.add(surfaceB);
-
if (changeFrameRateStrategy == Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS) {
verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
} else {
- verifyCompatibleAndStableFrameRate(frameRateA, surfaces);
+ verifyCompatibleAndStableFrameRate(frameRateA, FRAME_RATE_TOLERANCE_STRICT,
+ surfaceA, surfaceB);
}
verifyModeSwitchesDontChangeResolution(initialNumEvents,
@@ -800,7 +794,8 @@
if (changeFrameRateStrategy == Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS) {
verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
} else {
- verifyCompatibleAndStableFrameRate(frameRateB, surfaces);
+ verifyCompatibleAndStableFrameRate(frameRateB, FRAME_RATE_TOLERANCE_STRICT,
+ surfaceA, surfaceB);
}
verifyModeSwitchesDontChangeResolution(initialNumEvents,
mModeChangedEvents.size());
@@ -850,7 +845,7 @@
}
public void testInvalidParams() throws InterruptedException {
- runTestsWithPreconditions(api -> testInvalidParams(api), "invalid params behavior");
+ runTestsWithPreconditions(this::testInvalidParams, "invalid params behavior");
}
private void runOneSurfaceTest(Api api, OneSurfaceTestInterface test)
@@ -861,9 +856,6 @@
"testSurface", mSurfaceView.getHolder().getSurfaceFrame(),
/*visible=*/true, Color.RED);
- ArrayList<TestSurface> surfaces = new ArrayList<>();
- surfaces.add(surface);
-
test.run(surface);
} finally {
if (surface != null) {
@@ -872,24 +864,6 @@
}
}
- private void testSwitching(TestSurface surface, List<Float> frameRates, boolean expectSwitch,
- int changeFrameRateStrategy) throws InterruptedException {
- for (float frameRate : frameRates) {
- int initialNumEvents = mModeChangedEvents.size();
- surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
- changeFrameRateStrategy);
-
- if (expectSwitch) {
- verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
- }
- if (changeFrameRateStrategy == Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS) {
- verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
- }
- verifyModeSwitchesDontChangeResolution(initialNumEvents,
- mModeChangedEvents.size());
- }
- }
-
private void testMatchContentFramerate_None(Api api) throws InterruptedException {
runOneSurfaceTest(api, (TestSurface surface) -> {
Display display = getDisplay();
@@ -898,18 +872,18 @@
for (float frameRate : frameRates) {
int initialNumEvents = mModeChangedEvents.size();
- surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+ surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
Surface.CHANGE_FRAME_RATE_ALWAYS);
- assertTrue("Mode switches are not expected but these were detected "
- + modeSwitchesToString(initialNumEvents, mModeChangedEvents.size()),
- mModeChangedEvents.size() == initialNumEvents);
+ assertEquals("Mode switches are not expected but these were detected "
+ + modeSwitchesToString(initialNumEvents, mModeChangedEvents.size()),
+ mModeChangedEvents.size(), initialNumEvents);
}
});
}
public void testMatchContentFramerate_None() throws InterruptedException {
- runTestsWithPreconditions(api -> testMatchContentFramerate_None(api),
+ runTestsWithPreconditions(this::testMatchContentFramerate_None,
"testMatchContentFramerate_None");
}
@@ -922,27 +896,27 @@
for (float frameRate : frameRatesToTest) {
int initialNumEvents = mModeChangedEvents.size();
- surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+ surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
Surface.CHANGE_FRAME_RATE_ALWAYS);
- verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
+ verifyCompatibleAndStableFrameRate(frameRate, FRAME_RATE_TOLERANCE_STRICT, surface);
verifyModeSwitchesDontChangeResolution(initialNumEvents,
mModeChangedEvents.size());
}
// Reset to default
- surface.setFrameRate(0.f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+ surface.setFrameRate(0.f, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
Surface.CHANGE_FRAME_RATE_ALWAYS);
// Wait for potential mode switches.
- verifyCompatibleAndStableFrameRate(0, Arrays.asList(surface));
+ waitForStableFrameRate(surface);
currentMode = display.getMode();
List<Float> seamedRefreshRates = getSeamedRefreshRates(currentMode, display);
for (float frameRate : seamedRefreshRates) {
int initialNumEvents = mModeChangedEvents.size();
- surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+ surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
Surface.CHANGE_FRAME_RATE_ALWAYS);
// Mode switches may have occurred, make sure they were all seamless.
@@ -954,7 +928,7 @@
}
public void testMatchContentFramerate_Auto() throws InterruptedException {
- runTestsWithPreconditions(api -> testMatchContentFramerate_Auto(api),
+ runTestsWithPreconditions(this::testMatchContentFramerate_Auto,
"testMatchContentFramerate_Auto");
}
@@ -964,10 +938,10 @@
List<Float> frameRates = getRefreshRates(display.getMode(), display);
for (float frameRate : frameRates) {
int initialNumEvents = mModeChangedEvents.size();
- surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+ surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
Surface.CHANGE_FRAME_RATE_ALWAYS);
- verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
+ verifyCompatibleAndStableFrameRate(frameRate, FRAME_RATE_TOLERANCE_STRICT, surface);
verifyModeSwitchesDontChangeResolution(initialNumEvents,
mModeChangedEvents.size());
}
@@ -975,7 +949,7 @@
}
public void testMatchContentFramerate_Always() throws InterruptedException {
- runTestsWithPreconditions(api -> testMatchContentFramerate_Always(api),
+ runTestsWithPreconditions(this::testMatchContentFramerate_Always,
"testMatchContentFramerate_Always");
}
diff --git a/tests/tests/graphics/src/android/graphics/cts/HardwareRendererTest.java b/tests/tests/graphics/src/android/graphics/cts/HardwareRendererTest.java
new file mode 100644
index 0000000..7ec4fa9
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/HardwareRendererTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.HardwareRenderer;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class HardwareRendererTest {
+
+ @Test
+ public void isDrawingEnabled_defaultsTrue() {
+ assertThat(HardwareRenderer.isDrawingEnabled()).isTrue();
+ }
+
+ @Test
+ public void setDrawingEnabled() {
+ HardwareRenderer.setDrawingEnabled(false);
+
+ assertThat(HardwareRenderer.isDrawingEnabled()).isFalse();
+
+ HardwareRenderer.setDrawingEnabled(true);
+ assertThat(HardwareRenderer.isDrawingEnabled()).isTrue();
+ }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/NinePatchTest.java b/tests/tests/graphics/src/android/graphics/cts/NinePatchTest.java
index a4915b7..f829e80 100644
--- a/tests/tests/graphics/src/android/graphics/cts/NinePatchTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/NinePatchTest.java
@@ -86,6 +86,16 @@
mNinePatch = new NinePatch(mBitmap, mChunk, NAME);
assertEquals(mBitmap, mNinePatch.getBitmap());
assertEquals(NAME, mNinePatch.getName());
+
+ boolean caughtException = false;
+ try {
+ mNinePatch = new NinePatch(mBitmap, null);
+ } catch (Exception e) {
+ // No need to catch it, just asserting that it was thrown
+ caughtException = true;
+ } finally {
+ assertTrue(caughtException);
+ }
}
@Test
diff --git a/tests/tests/graphics/src/android/graphics/cts/OutlineTest.java b/tests/tests/graphics/src/android/graphics/cts/OutlineTest.java
index d53cec7..7fb066a 100644
--- a/tests/tests/graphics/src/android/graphics/cts/OutlineTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/OutlineTest.java
@@ -162,17 +162,17 @@
Rect outRect = new Rect();
outline.setOval(0, 0, 50, 51); // different x & y radii, so not round rect
assertFalse(outline.getRect(outRect)); // not round rect, doesn't work
- assertFalse(outline.canClip()); // not round rect, doesn't work
+ assertTrue(outline.canClip());
assertFalse(outline.isEmpty());
outline.setOval(0, 0, 50, 50); // same x & y radii, so round rect
assertTrue(outline.getRect(outRect)); // is round rect, so works
- assertTrue(outline.canClip()); // is round rect, so works
+ assertTrue(outline.canClip());
assertFalse(outline.isEmpty());
outline.setOval(new Rect(0, 0, 50, 50)); // same x & y radii, so round rect
assertTrue(outline.getRect(outRect)); // is round rect, so works
- assertTrue(outline.canClip()); // is round rect, so works
+ assertTrue(outline.canClip());
assertFalse(outline.isEmpty());
}
@@ -187,6 +187,8 @@
path.addCircle(50, 50, 50, Path.Direction.CW);
outline.setPath(path);
+ assertTrue(outline.canClip());
+
assertFalse(outline.isEmpty());
}
@@ -239,5 +241,10 @@
outline.offset(-5, -10);
assertTrue(outline.getRect(outRect));
assertEquals(new Rect(0, 0, 10, 10), outRect);
+
+ // Test cumulative effects
+ outline.offset(-5, -10);
+ assertTrue(outline.getRect(outRect));
+ assertEquals(new Rect(-5, -10, 5, 0), outRect);
}
}
diff --git a/tests/tests/graphics/src/android/graphics/cts/SetFrameRateTest.java b/tests/tests/graphics/src/android/graphics/cts/SetFrameRateTest.java
index 1b41d09..379a732 100644
--- a/tests/tests/graphics/src/android/graphics/cts/SetFrameRateTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/SetFrameRateTest.java
@@ -20,16 +20,16 @@
import static org.junit.Assert.assertTrue;
-import android.app.UiAutomation;
+import android.Manifest;
import android.content.Context;
-import android.util.Log;
+import android.hardware.display.DisplayManager;
import android.view.Surface;
-import android.view.SurfaceControl;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
import com.android.compatibility.common.util.DisplayUtil;
import org.junit.After;
@@ -46,38 +46,39 @@
@Rule
public ActivityTestRule<FrameRateCtsActivity> mActivityRule =
new ActivityTestRule<>(FrameRateCtsActivity.class);
- private long mFrameRateFlexibilityToken;
+
+ @Rule
+ public final AdoptShellPermissionsRule mShellPermissionsRule =
+ new AdoptShellPermissionsRule(getInstrumentation().getUiAutomation(),
+ Manifest.permission.HDMI_CEC,
+ Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS,
+ Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE);
+
+ private DisplayManager mDisplayManager;
+ private int mInitialRefreshRateSwitchingType;
@Before
public void setUp() throws Exception {
- // Surface flinger requires the ACCESS_SURFACE_FLINGER permission to acquire a frame
- // rate flexibility token. Switch to shell permission identity so we'll have the
- // necessary permission when surface flinger checks.
- UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
- uiAutomation.adoptShellPermissionIdentity();
-
Context context = getInstrumentation().getTargetContext();
assertTrue("Physical display is expected.", DisplayUtil.isDisplayConnected(context));
- try {
- // Take ownership of the frame rate flexibility token, if we were able
- // to get one - we'll release it in tearDown().
- mFrameRateFlexibilityToken = SurfaceControl.acquireFrameRateFlexibilityToken();
- } finally {
- uiAutomation.dropShellPermissionIdentity();
- }
+ FrameRateCtsActivity activity = mActivityRule.getActivity();
- if (mFrameRateFlexibilityToken == 0) {
- Log.e(TAG, "Failed to acquire frame rate flexibility token."
- + " SetFrameRate tests may fail.");
- }
+ // Prevent DisplayManager from limiting the allowed refresh rate range based on
+ // non-app policies (e.g. low battery, user settings, etc).
+ mDisplayManager = activity.getSystemService(DisplayManager.class);
+ mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
+
+ mInitialRefreshRateSwitchingType = DisplayUtil.getRefreshRateSwitchingType(mDisplayManager);
+ mDisplayManager.setRefreshRateSwitchingType(
+ DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
}
@After
public void tearDown() {
- if (mFrameRateFlexibilityToken != 0) {
- SurfaceControl.releaseFrameRateFlexibilityToken(mFrameRateFlexibilityToken);
- mFrameRateFlexibilityToken = 0;
+ if (mDisplayManager != null) {
+ mDisplayManager.setRefreshRateSwitchingType(mInitialRefreshRateSwitchingType);
+ mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
}
}
diff --git a/tests/tests/graphics/src/android/graphics/cts/SystemPaletteTest.java b/tests/tests/graphics/src/android/graphics/cts/SystemPaletteTest.java
index e5a3ed7..bf17876 100644
--- a/tests/tests/graphics/src/android/graphics/cts/SystemPaletteTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/SystemPaletteTest.java
@@ -18,36 +18,40 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
import static com.google.common.truth.Truth.assertWithMessage;
import android.R;
import android.content.Context;
import android.graphics.Color;
-import android.graphics.cts.utils.Cam;
+import android.provider.Settings;
+import android.util.Log;
import android.util.Pair;
-
import androidx.annotation.ColorInt;
import androidx.core.graphics.ColorUtils;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.PollingCheck;
+
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
-import java.util.ArrayList;
+import java.io.IOException;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
public class SystemPaletteTest {
- // Hue goes from 0 to 360
- private static final int MAX_HUE_DISTANCE = 15;
+ private static final boolean DEBUG = false;
+ private static final String TAG = "SystemPaletteTest";
@Test
public void testShades0and1000() {
@@ -70,78 +74,67 @@
}
@Test
- public void testAllColorsBelongToSameFamily() {
+ public void testThemeStyles() {
final Context context = getInstrumentation().getTargetContext();
- List<int[]> allPalettes = Arrays.asList(getAllAccent1Colors(context),
- getAllAccent2Colors(context), getAllAccent3Colors(context),
- getAllNeutral1Colors(context), getAllNeutral2Colors(context));
+ forEachThemeDefinition((color, style, expectedPalette) -> {
+ // Update setting, so system colors will change
+ runWithShellPermissionIdentity(() -> {
+ Settings.Secure.putString(context.getContentResolver(),
+ Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ "{\"android.theme.customization.system_palette\":\"" + color
+ + "\",\"android.theme.customization.theme_style\":\"" + style
+ + "\"}");
+ });
- final float[] tones = {100, 99, 95, 90, 80, 70, 60, 49, 40, 30, 20, 10, 0};
- for (int[] palette : allPalettes) {
- // Determine the median hue of the palette. Each color in the palette colors will have
- // its hue measured against the median hue. If the difference is too large, the test
- // fails.
- List<Float> hues = new ArrayList<>();
- for (int i = 0; i < palette.length - 1; i++) {
- // Avoid measuring hue of colors above 90 or below 10 in tone.
- //
- // Converting from HCT to sRGB from display quantizes colors - i.e. not every
- // HCT color can be expressed in sRGB. As colors approach the extreme tones, white
- // at 100 and black at 0, hues begin overlapping overlay - made up example: hues
- // 110 to 128 at tone 95, when mapped to sRGB for display, all end up being measured
- // as hue 114.
- final float tone = tones[i];
- if (tone <= 10.0 || tone > 90.0) {
- continue;
- }
- final Cam cam = Cam.fromInt(palette[i]);
- hues.add(cam.getHue());
- }
- Collections.sort(hues);
- final float medianHue = hues.get(hues.size() / 2);
+ final int[] allColors = new int[65];
+ new PollingCheck(5_000L, "Invalid tonal palettes for " + color + " " + style) {
+ @Override
+ protected boolean check() {
- // Measure the hue of each color in the palette against the median hue.
- for (int i = 0; i < palette.length - 1; i++) {
- final float tone = tones[i];
- // Skip testing hue of extreme tones, due to overlap due to quantization that occurs
- // when converting from HCT to sRGB for display.
- if (tone <= 10.0 || tone > 90.0) {
- continue;
+ System.arraycopy(getAllAccent1Colors(context), 0, allColors, 0, 13);
+ System.arraycopy(getAllAccent2Colors(context), 0, allColors, 13, 13);
+ System.arraycopy(getAllAccent3Colors(context), 0, allColors, 26, 13);
+ System.arraycopy(getAllNeutral1Colors(context), 0, allColors, 39, 13);
+ System.arraycopy(getAllNeutral2Colors(context), 0, allColors, 52, 13);
+
+ if (DEBUG) {
+ Log.d(TAG, "Expected:\n" + Arrays.toString(expectedPalette)
+ + "\nActual:\n" + Arrays.toString(allColors));
+ }
+
+ return Arrays.equals(allColors, expectedPalette);
}
- final Cam cam = Cam.fromInt(palette[i]);
- final float hue = cam.getHue();
- final boolean hueWithinTolerance = deltaHueWithinTolerance(hue, medianHue);
- assertWithMessage("Color " + toHctString(cam)
- + " has different hue compared to median hue " + Math.round(medianHue)
- + " of palette: " + Arrays.toString(palette))
- .that(hueWithinTolerance).isTrue();
+ }.run();
+ });
+ }
+
+ private void forEachThemeDefinition(ThemeEvaluator evaluator) {
+ final Context context = getInstrumentation().getTargetContext();
+ final XmlPullParser parser = context.getResources()
+ .getXml(android.graphics.cts.R.xml.valid_themes);
+ try {
+ parser.next();
+ parser.next();
+ parser.require(XmlPullParser.START_TAG, null, "themes");
+ while (parser.next() != XmlPullParser.END_TAG) {
+ parser.require(XmlPullParser.START_TAG, null, "theme");
+ final String color = parser.getAttributeValue(null, "color");
+ while (parser.next() != XmlPullParser.END_TAG) {
+ String styleName = parser.getName();
+ parser.next();
+ int[] colors = Arrays.stream(parser.getText().split(","))
+ .mapToInt(s -> Color.parseColor("#" + s))
+ .toArray();
+ parser.next();
+ parser.require(XmlPullParser.END_TAG, null, styleName);
+ evaluator.apply(color, styleName.toUpperCase(), colors);
+ }
}
+ } catch (XmlPullParserException | IOException e) {
+ throw new RuntimeException("Error parsing xml", e);
}
}
- private static String toHctString(Cam cam) {
- final double[] labColor = new double[3];
- ColorUtils.colorToLAB(cam.viewedInSrgb(), labColor);
- return "H" + Math.round(cam.getHue()) + " C" + Math.round(cam.getChroma()) + " T"
- + Math.round(labColor[0]);
- }
-
- /**
- * Compare if color A and B have similar hue, in gCAM space.
- *
- * @param colorA Color 1
- * @param colorB Color 2
- * @return True when colors have similar hue.
- */
- private boolean deltaHueWithinTolerance(float hueA, float hueB) {
-
- float hue1 = Math.max(hueA, hueB);
- float hue2 = Math.min(hueA, hueB);
-
- float diffDegrees = 180.0f - Math.abs(Math.abs(hue1 - hue2) - 180.0f);
- return diffDegrees < MAX_HUE_DISTANCE;
- }
-
@Test
public void testColorsMatchExpectedLuminance() {
final Context context = getInstrumentation().getTargetContext();
@@ -318,4 +311,8 @@
colors[12] = context.getColor(R.color.system_neutral2_1000);
return colors;
}
+
+ private interface ThemeEvaluator {
+ void apply(String color, String style, int[] expectedPalette);
+ }
}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AdaptiveIconDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AdaptiveIconDrawableTest.java
index a0ba34b..fce9b34 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AdaptiveIconDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AdaptiveIconDrawableTest.java
@@ -55,6 +55,7 @@
@Test
public void testConstructor() {
new AdaptiveIconDrawable(null, null);
+ new AdaptiveIconDrawable(null, null, null);
}
@Test
@@ -142,6 +143,21 @@
assertEquals(128, iconDrawable.getAlpha());
}
+ @Test
+ public void testGetMonochrome() {
+ ColorDrawable drawable = new ColorDrawable(Color.RED);
+ AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(null, null, drawable);
+ assertEquals(drawable, iconDrawable.getMonochrome());
+ }
+
+ @Test
+ public void testMonochromeInflated() {
+ Resources r = InstrumentationRegistry.getTargetContext().getResources();
+ AdaptiveIconDrawable iconDrawable = (AdaptiveIconDrawable) r.getDrawable(
+ R.drawable.adaptiveicondrawable);
+ assertNotNull(iconDrawable.getMonochrome());
+ }
+
/**
* When setBound isn't called before draw method is called.
* Nothing is drawn.
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
index da2eb16..92c8428 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
@@ -48,8 +48,8 @@
import androidx.test.rule.ActivityTestRule;
import com.android.compatibility.common.util.BitmapUtils;
-import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.Rule;
import org.junit.Test;
@@ -92,7 +92,7 @@
private void setupActivity() {
mActivity = mActivityRule.getActivity();
- PollingCheck.waitFor(mActivity::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity);
mImageView = mActivity.findViewById(R.id.animated_image);
}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
index d2fbcf8..a8e6d75 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
@@ -715,6 +715,62 @@
}
@Test
+ public void testGradientColorInflationWithThemeAndNonThemeResources() {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ final Theme theme = context.getResources().newTheme();
+ theme.applyStyle(R.style.Theme_MixedGradientTheme, true);
+ final Theme ctxTheme = context.getTheme();
+ ctxTheme.setTo(theme);
+
+ GradientDrawable drawable = (GradientDrawable)
+ ctxTheme.getDrawable(R.drawable.gradientdrawable_color_mix_theme);
+
+ int[] colors = drawable.getColors();
+ drawable.setColors(colors);
+ assertEquals(3, colors.length);
+ assertEquals(context.getColor(R.color.colorAccent), colors[0]);
+ assertEquals(context.getColor(R.color.colorPrimary), colors[1]);
+ assertEquals(context.getColor(R.color.colorPrimaryDark), colors[2]);
+ }
+
+ @Test
+ public void testGradientColorInflationWithAllThemeResources() {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ final Theme theme = context.getResources().newTheme();
+ theme.applyStyle(R.style.Theme_MixedGradientTheme, true);
+ final Theme ctxTheme = context.getTheme();
+ ctxTheme.setTo(theme);
+
+ GradientDrawable drawable = (GradientDrawable)
+ ctxTheme.getDrawable(R.drawable.gradientdrawable_color_all_theme);
+
+ int[] colors = drawable.getColors();
+ drawable.setColors(colors);
+ assertEquals(3, colors.length);
+ assertEquals(context.getColor(R.color.colorPrimary), colors[0]);
+ assertEquals(context.getColor(R.color.colorPrimaryDark), colors[1]);
+ assertEquals(context.getColor(R.color.colorPrimaryDark), colors[2]);
+ }
+
+ @Test
+ public void testGradientColorNoCenterColorInflationWithThemeAndNonThemeResources() {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ final Theme theme = context.getResources().newTheme();
+ theme.applyStyle(R.style.Theme_MixedGradientTheme, true);
+ final Theme ctxTheme = context.getTheme();
+ ctxTheme.setTo(theme);
+
+ GradientDrawable drawable = (GradientDrawable)
+ ctxTheme.getDrawable(R.drawable.gradientdrawable_noCenterColor_all_theme);
+
+ int[] colors = drawable.getColors();
+ drawable.setColors(colors);
+ assertEquals(2, colors.length);
+ assertEquals(context.getColor(R.color.colorPrimary), colors[0]);
+ assertEquals(context.getColor(R.color.colorPrimaryDark), colors[1]);
+ }
+
+ @Test
public void testRadialInflationWithThemeAndNonThemeResources() {
final Context context = new ContextThemeWrapper(InstrumentationRegistry.getTargetContext(),
R.style.Theme_MixedGradientTheme);
diff --git a/tests/tests/graphics/src/android/graphics/fonts/FontManagerTest.java b/tests/tests/graphics/src/android/graphics/fonts/FontManagerTest.java
index 0e2711d..7725e9f 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/FontManagerTest.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/FontManagerTest.java
@@ -20,6 +20,7 @@
import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
@@ -271,5 +272,27 @@
}
}
+ @Test
+ public void fontManager_NotoColorEmojiAvailable() throws IOException {
+ FontConfig fontConfig = getFontConfig();
+ boolean hasNotoColorEmoji = false;
+
+ for (FontConfig.FontFamily family : fontConfig.getFontFamilies()) {
+
+ if (family.getLocaleList().size() == 1
+ && "Zsye".equals(family.getLocaleList().get(0).getScript())) {
+ if ("NotoColorEmoji".equals(family.getFontList().get(0).getPostScriptName())) {
+ hasNotoColorEmoji = true;
+ }
+ }
+ }
+
+ assertWithMessage("NotoColorEmoji must be included."
+ + "If you include your own font, place your emoji just before the "
+ + "NotoColorEmoji.ttf")
+ .that(hasNotoColorEmoji)
+ .isTrue();
+ }
+
// TODO: Add more tests once we sign test fonts.
}
diff --git a/tests/tests/graphics/src/android/graphics/text/cts/HyphenationTest.java b/tests/tests/graphics/src/android/graphics/text/cts/HyphenationTest.java
new file mode 100644
index 0000000..38ed6a5
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/text/cts/HyphenationTest.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.text.cts;
+
+import static android.graphics.text.LineBreaker.BREAK_STRATEGY_BALANCED;
+import static android.graphics.text.LineBreaker.HYPHENATION_FREQUENCY_FULL;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.text.LineBreaker;
+import android.graphics.text.MeasuredText;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+/**
+ * Verify the hyphenation pattern works as expected.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HyphenationTest {
+ private static Paint sPaint;
+
+ @BeforeClass
+ public static void classSetUp() {
+ sPaint = new Paint();
+ Context context = InstrumentationRegistry.getTargetContext();
+ AssetManager am = context.getAssets();
+ Typeface tf = new Typeface.Builder(am, "fonts/layout/linebreak.ttf").build();
+ sPaint.setTypeface(tf);
+ }
+
+ @Test
+ public void testAfrikaansPattern() {
+ final String locale = "af";
+ final String text = "zastavila zastavila zastavila zastavila";
+ final float textSize = 10.0f;
+ sPaint.setTextLocale(new Locale(locale));
+ sPaint.setTextSize(textSize);
+
+ // The visual BALANCED line break output is like
+ // | zastavila zas- |
+ // | tavila zasta- |
+ // | vila zastavila |
+ final LineBreaker.Result r = computeLineBreaks(text);
+
+ assertEquals(3, r.getLineCount());
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+ }
+
+ @Test
+ public void testAlbanianPattern() {
+ final String locale = "sq";
+ final String text = "vazhduar vazhduar vazhduar vazhduar";
+ final float textSize = 15.0f;
+ sPaint.setTextLocale(new Locale(locale));
+ sPaint.setTextSize(textSize);
+
+ // The visual BALANCED line break output is like
+ // | vazhdu- |
+ // | ar vazhdu- |
+ // | ar vazhdu- |
+ // | ar vazhduar |
+ final LineBreaker.Result r = computeLineBreaks(text);
+
+ assertEquals(4, r.getLineCount());
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(2));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(3));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(3));
+ }
+
+ @Test
+ public void testAmharicPattern() {
+ final String locale = "am";
+ final String text = "መጋበዛቸውን መጋበዛቸውን መጋበዛቸውን መጋበዛቸውን መጋበዛቸውን";
+ final float textSize = 10.0f;
+ sPaint.setTextLocale(new Locale(locale));
+ sPaint.setTextSize(textSize);
+
+ // The visual BALANCED line break output is like
+ // | መጋበዛቸውን መጋበዛቸ- |
+ // | ውን መጋበዛቸውን መ- |
+ // | ጋበዛቸውን መጋበዛቸውን |
+ final LineBreaker.Result r = computeLineBreaks(text);
+
+ assertEquals(3, r.getLineCount());
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+ }
+
+ @Test
+ public void testCzechPattern() {
+ final String locale = "cs";
+ final String text = "epidemiologická epidemiologická epidemiologická";
+ final float textSize = 15.0f;
+ sPaint.setTextLocale(new Locale(locale));
+ sPaint.setTextSize(textSize);
+
+ // The visual BALANCED line break output is like
+ // | epidemiolo- |
+ // | gická epi- |
+ // | demiolo- |
+ // | gická epide- |
+ // | miologická |
+ final LineBreaker.Result r = computeLineBreaks(text);
+
+ assertEquals(5, r.getLineCount());
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(2));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(3));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(3));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(4));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(4));
+ }
+
+ @Test
+ public void testDutchPattern() {
+ final String locale = "nl";
+ final String text = "beschuldigt beschuldigt beschuldigt beschuldigt";
+ final float textSize = 10.0f;
+ sPaint.setTextLocale(new Locale(locale));
+ sPaint.setTextSize(textSize);
+
+ // The visual BALANCED line break output is like
+ // | beschuldigt be- |
+ // | schuldigt beschul- |
+ // | digt beschuldigt |
+ final LineBreaker.Result r = computeLineBreaks(text);
+
+ assertEquals(3, r.getLineCount());
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+ }
+
+ @Test
+ public void testEnglishPattern() {
+ final String locale = "en";
+ final String text = "hyphenation hyphenation hyphenation hyphenation";
+ final float textSize = 10.0f;
+ sPaint.setTextLocale(new Locale(locale));
+ sPaint.setTextSize(textSize);
+
+ // The visual BALANCED line break output is like
+ // | hyphenation hy- |
+ // | phenation hyphen- |
+ // | ation hyphenation |
+ final LineBreaker.Result r = computeLineBreaks(text);
+
+ assertEquals(3, r.getLineCount());
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+ }
+
+ @Test
+ public void testGalicianPattern() {
+ final String locale = "gl";
+ final String text = "tecnoloxía tecnoloxía tecnoloxía";
+ final float textSize = 10.0f;
+ sPaint.setTextLocale(new Locale(locale));
+ sPaint.setTextSize(textSize);
+
+ // The visual BALANCED line break output is like
+ // | tecnoloxía tecno- |
+ // | loxía tecnoloxía |
+ final LineBreaker.Result r = computeLineBreaks(text);
+
+ assertEquals(2, r.getLineCount());
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(1));
+ }
+
+ @Test
+ public void testGeorgianPattern() {
+ final String locale = "ka";
+ final String text = "ინფორმაციით ინფორმაციით ინფორმაციით ინფორმაციით";
+ final float textSize = 10.0f;
+ sPaint.setTextLocale(new Locale(locale));
+ sPaint.setTextSize(textSize);
+
+ // The visual BALANCED line break output is like
+ // | ინფორმაცი- |
+ // | ით ინფორმაცი- |
+ // | ით ინფორმაცი- |
+ // | ით ინფორმაციით |
+ final LineBreaker.Result r = computeLineBreaks(text);
+
+ assertEquals(3, r.getLineCount());
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+ }
+
+ @Test
+ public void testGreekPattern() {
+ final String locale = "el";
+ final String text = "εργαζόμενους εργαζόμενους εργαζόμενους";
+ final float textSize = 10.0f;
+ sPaint.setTextLocale(new Locale(locale));
+ sPaint.setTextSize(textSize);
+
+ // The visual BALANCED line break output is like
+ // | εργαζόμενους ερ- |
+ // | γαζόμενους ερ- |
+ // | γαζόμενους |
+ final LineBreaker.Result r = computeLineBreaks(text);
+
+ assertEquals(3, r.getLineCount());
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+ }
+
+ @Test
+ public void testItalianPattern() {
+ final String locale = "it";
+ final String text = "Assicurati Assicurati Assicurati Assicurati";
+ final float textSize = 10.0f;
+ sPaint.setTextLocale(new Locale(locale));
+ sPaint.setTextSize(textSize);
+
+ // The visual BALANCED line break output is like
+ // | Assicurati Assi- |
+ // | curati Assicu- |
+ // | rati Assicurati |
+ final LineBreaker.Result r = computeLineBreaks(text);
+
+ assertEquals(3, r.getLineCount());
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+ }
+
+ @Test
+ public void testLatvianPattern() {
+ final String locale = "lv";
+ final String text = "verifikāciju verifikāciju verifikāciju verifikāciju";
+ final float textSize = 10.0f;
+ sPaint.setTextLocale(new Locale(locale));
+ sPaint.setTextSize(textSize);
+
+ // The visual BALANCED line break output is like
+ // | verifikāciju veri- |
+ // | fikāciju verifikā- |
+ // | ciju verifikāciju |
+ final LineBreaker.Result r = computeLineBreaks(text);
+
+ assertEquals(3, r.getLineCount());
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+ }
+
+ @Test
+ public void testLithuanianPattern() {
+ final String locale = "lt";
+ final String text = "Pasirūpinkite Pasirūpinkite Pasirūpinkite Pasirūpinkite";
+ final float textSize = 10.0f;
+ sPaint.setTextLocale(new Locale(locale));
+ sPaint.setTextSize(textSize);
+
+ // The visual BALANCED line break output is like
+ // | Pasirūpinki- |
+ // | te Pasirūpinki- |
+ // | te Pasirūpinki- |
+ // | te Pasirūpinkite |
+ final LineBreaker.Result r = computeLineBreaks(text);
+
+ assertEquals(4, r.getLineCount());
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(2));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(3));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(3));
+ }
+
+ @Test
+ public void testSlovakPattern() {
+ final String locale = "sk";
+ final String text = "epidemiologická epidemiologická epidemiologická";
+ final float textSize = 15.0f;
+ sPaint.setTextLocale(new Locale(locale));
+ sPaint.setTextSize(textSize);
+
+ // The visual BALANCED line break output is like
+ // | epidemiolo- |
+ // | gická epi- |
+ // | demiolo- |
+ // | gická epide- |
+ // | miologická |
+ final LineBreaker.Result r = computeLineBreaks(text);
+
+ assertEquals(5, r.getLineCount());
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(2));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(3));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(3));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(4));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(4));
+ }
+
+ @Test
+ public void testSwedishPattern() {
+ final String locale = "sv";
+ final String text = "nederbörd nederbörd nederbörd nederbörd";
+ final float textSize = 10.0f;
+ sPaint.setTextLocale(new Locale(locale));
+ sPaint.setTextSize(textSize);
+
+ // The visual BALANCED line break output is like
+ // | nederbörd ne- |
+ // | derbörd neder- |
+ // | börd nederbörd |
+ final LineBreaker.Result r = computeLineBreaks(text);
+
+ assertEquals(3, r.getLineCount());
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+ }
+
+ @Test
+ public void testUkrainianPattern() {
+ final String locale = "uk";
+ final String text = "Увімкніть Увімкніть Увімкніть Увімкніть";
+ final float textSize = 10.0f;
+ sPaint.setTextLocale(new Locale(locale));
+ sPaint.setTextSize(textSize);
+
+ // The visual BALANCED line break output is like
+ // | Увімкніть Уві- |
+ // | мкніть Уві- |
+ // | мкніть Увімкніть |
+ final LineBreaker.Result r = computeLineBreaks(text);
+
+ assertEquals(3, r.getLineCount());
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+ }
+
+ private LineBreaker.Result computeLineBreaks(String text) {
+ final LineBreaker lb = new LineBreaker.Builder()
+ .setBreakStrategy(BREAK_STRATEGY_BALANCED)
+ .setHyphenationFrequency(HYPHENATION_FREQUENCY_FULL)
+ .build();
+ final LineBreaker.ParagraphConstraints c = new LineBreaker.ParagraphConstraints();
+ c.setWidth(180f);
+ MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
+ .setComputeHyphenation(MeasuredText.Builder.HYPHENATION_MODE_NORMAL)
+ .appendStyleRun(sPaint, text.length(), false)
+ .build();
+ return lb.computeLineBreaks(mt, c, 0);
+ }
+}
diff --git a/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java b/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
index 5a20570..c02f052 100644
--- a/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
+++ b/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
@@ -584,6 +584,80 @@
}
@Test
+ public void testLineBreak_Balanced_Hyphenation() {
+ // The visual BALANCED line break output is like
+ // |hyphenation hy- |
+ // |phenation hyphen- |
+ // |ation hyphenation |
+ final String text = "hyphenation hyphenation hyphenation hyphenation";
+ final LineBreaker lb = new LineBreaker.Builder()
+ .setBreakStrategy(BREAK_STRATEGY_BALANCED)
+ .setHyphenationFrequency(HYPHENATION_FREQUENCY_FULL)
+ .build();
+ final ParagraphConstraints c = new ParagraphConstraints();
+ c.setWidth(180f);
+ MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
+ .setComputeHyphenation(MeasuredText.Builder.HYPHENATION_MODE_NORMAL)
+ .appendStyleRun(sPaint, text.length(), false)
+ .build();
+ final Result r = lb.computeLineBreaks(mt, c, 0);
+ assertEquals(3, r.getLineCount());
+ assertEquals(14, r.getLineBreakOffset(0));
+ assertEquals(30, r.getLineBreakOffset(1));
+ assertEquals(47, r.getLineBreakOffset(2));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+ assertFalse(r.hasLineTab(0));
+ assertFalse(r.hasLineTab(1));
+ assertFalse(r.hasLineTab(2));
+ assertEquals(150.0f, r.getLineWidth(0), 0.0f);
+ assertEquals(170.0f, r.getLineWidth(1), 0.0f);
+ assertEquals(170.0f, r.getLineWidth(2), 0.0f);
+ }
+
+ @Test
+ public void testLineBreak_Balanced_Hyphenation_IgnoreKerning() {
+ // The visual BALANCED line break output is like
+ // |hyphenation hy- |
+ // |phenation hyphen- |
+ // |ation hyphenation |
+ //
+ // Note: The line break result should be same to non-fast version.
+ final String text = "hyphenation hyphenation hyphenation hyphenation";
+ final LineBreaker lb = new LineBreaker.Builder()
+ .setBreakStrategy(BREAK_STRATEGY_BALANCED)
+ .setHyphenationFrequency(HYPHENATION_FREQUENCY_FULL)
+ .build();
+ final ParagraphConstraints c = new ParagraphConstraints();
+ c.setWidth(180f);
+ MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
+ .setComputeHyphenation(MeasuredText.Builder.HYPHENATION_MODE_FAST)
+ .appendStyleRun(sPaint, text.length(), false)
+ .build();
+ final Result r = lb.computeLineBreaks(mt, c, 0);
+ assertEquals(3, r.getLineCount());
+ assertEquals(14, r.getLineBreakOffset(0));
+ assertEquals(30, r.getLineBreakOffset(1));
+ assertEquals(47, r.getLineBreakOffset(2));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+ assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+ assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+ assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+ assertFalse(r.hasLineTab(0));
+ assertFalse(r.hasLineTab(1));
+ assertFalse(r.hasLineTab(2));
+ assertEquals(150.0f, r.getLineWidth(0), 0.0f);
+ assertEquals(170.0f, r.getLineWidth(1), 0.0f);
+ assertEquals(170.0f, r.getLineWidth(2), 0.0f);
+ }
+
+ @Test
public void testLineBreak_ZeroWidthTab() {
final String text = "Hi, \tWorld.";
final LineBreaker lb = new LineBreaker.Builder()
diff --git a/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java b/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java
index 52a2a74..085ef45 100644
--- a/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java
+++ b/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java
@@ -28,6 +28,7 @@
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
+import android.graphics.text.LineBreakConfig;
import android.graphics.text.MeasuredText;
import android.text.PrecomputedText;
import android.text.SpannableStringBuilder;
@@ -61,6 +62,11 @@
String text = "Hello, World";
new MeasuredText.Builder(text.toCharArray())
.appendStyleRun(sPaint, text.length(), false /* isRtl */).build();
+
+ LineBreakConfig config = new LineBreakConfig();
+ config.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_LOOSE);
+ new MeasuredText.Builder(text.toCharArray())
+ .appendStyleRun(sPaint, config, text.length(), false /* isRtl */).build();
}
@Test
diff --git a/tests/tests/hardware/Android.bp b/tests/tests/hardware/Android.bp
index 7617806..81f02a4 100644
--- a/tests/tests/hardware/Android.bp
+++ b/tests/tests/hardware/Android.bp
@@ -44,6 +44,9 @@
"libctshardware_jni",
"libnativehelper_compat_libc++",
],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
sdk_version: "test_current",
}
diff --git a/tests/tests/hardware/res/raw/google_pixelusbcearbuds_keyeventtests.json b/tests/tests/hardware/res/raw/google_pixelusbcearbuds_keyeventtests.json
new file mode 100644
index 0000000..1ab4894
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_pixelusbcearbuds_keyeventtests.json
@@ -0,0 +1,51 @@
+[
+ {
+ "name": "Initial check - no events should be produced",
+ "reports": [
+ [0x01, 0x00],
+ [0x01, 0x00]
+ ],
+ "source": "KEYBOARD",
+ "events": [
+ ]
+ },
+
+ {
+ "name": "Press VOLUME_UP",
+ "reports": [
+ [0x01, 0x01],
+ [0x01, 0x00]
+ ],
+ "source": "KEYBOARD",
+ "events": [
+ {"action": "DOWN", "keycode": "VOLUME_UP"},
+ {"action": "UP", "keycode": "VOLUME_UP"}
+ ]
+ },
+
+ {
+ "name": "Press VOLUME_DOWN",
+ "reports": [
+ [0x01, 0x02],
+ [0x01, 0x00]
+ ],
+ "source": "KEYBOARD",
+ "events": [
+ {"action": "DOWN", "keycode": "VOLUME_DOWN"},
+ {"action": "UP", "keycode": "VOLUME_DOWN"}
+ ]
+ },
+
+ {
+ "name": "Press play/pause (black round middle button)",
+ "reports": [
+ [0x01, 0x04],
+ [0x01, 0x00]
+ ],
+ "source": "KEYBOARD",
+ "events": [
+ {"action": "DOWN", "keycode": "MEDIA_PLAY_PAUSE"},
+ {"action": "UP", "keycode": "MEDIA_PLAY_PAUSE"}
+ ]
+ }
+]
diff --git a/tests/tests/hardware/res/raw/google_pixelusbcearbuds_register.json b/tests/tests/hardware/res/raw/google_pixelusbcearbuds_register.json
new file mode 100644
index 0000000..1b87f57
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_pixelusbcearbuds_register.json
@@ -0,0 +1,13 @@
+{
+ "id": 1,
+ "command": "register",
+ "name": "Google Pixel USB-C earbuds",
+ "vid": 0x18d1,
+ "pid": 0x5003,
+ "bus": "usb",
+ "source": "KEYBOARD",
+ "descriptor": [0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x01, 0x15, 0x00, 0x25, 0x01, 0x75,
+ 0x01, 0x95, 0x02, 0x09, 0xe9, 0x09, 0xea, 0x81, 0x02, 0x95, 0x01, 0x09, 0xcd, 0x81, 0x02,
+ 0x95, 0x05, 0x81, 0x01, 0x85, 0x04, 0x09, 0x00, 0x75, 0x08, 0x95, 0x26, 0x91, 0x02, 0x85,
+ 0x05, 0x09, 0x00, 0x95, 0x22, 0x81, 0x02, 0xc0]
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/DataSpaceTest.java b/tests/tests/hardware/src/android/hardware/cts/DataSpaceTest.java
new file mode 100644
index 0000000..f7430d6
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/DataSpaceTest.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.cts;
+
+import static android.opengl.GLES20.glDeleteTextures;
+import static android.opengl.GLES20.glGenTextures;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.DataSpace;
+import android.media.Image;
+import android.media.ImageReader;
+import android.media.ImageWriter;
+import android.opengl.EGL14;
+import android.opengl.EGLConfig;
+import android.opengl.EGLContext;
+import android.opengl.EGLDisplay;
+import android.opengl.EGLSurface;
+import android.view.Surface;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DataSpaceTest {
+ private SurfaceTexture mSurfaceTexture;
+ private Surface mSurface;
+ private int[] mTex;
+ private ImageWriter mWriter;
+ private ImageReader mReader;
+
+ private EGLDisplay mEglDisplay = EGL14.EGL_NO_DISPLAY;
+ private EGLConfig mEglConfig = null;
+ private EGLSurface mEglSurface = EGL14.EGL_NO_SURFACE;
+ private EGLContext mEglContext = EGL14.EGL_NO_CONTEXT;
+
+ @UiThreadTest
+ @Before
+ public void setUp() throws Throwable {
+ mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+ if (mEglDisplay == EGL14.EGL_NO_DISPLAY) {
+ throw new RuntimeException("no EGL display");
+ }
+ int[] major = new int[1];
+ int[] minor = new int[1];
+ if (!EGL14.eglInitialize(mEglDisplay, major, 0, minor, 0)) {
+ throw new RuntimeException("error in eglInitialize");
+ }
+
+ // If we could rely on having EGL_KHR_surfaceless_context and EGL_KHR_context_no_config, we
+ // wouldn't have to create a config or pbuffer at all.
+
+ int[] numConfigs = new int[1];
+ EGLConfig[] configs = new EGLConfig[1];
+ if (!EGL14.eglChooseConfig(mEglDisplay, new int[] {
+ EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
+ EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
+ EGL14.EGL_NONE}, 0, configs, 0, 1, numConfigs, 0)) {
+ throw new RuntimeException("eglChooseConfig failed");
+ }
+ mEglConfig = configs[0];
+
+ mEglSurface = EGL14.eglCreatePbufferSurface(mEglDisplay, mEglConfig,
+ new int[] {EGL14.EGL_WIDTH, 1, EGL14.EGL_HEIGHT, 1, EGL14.EGL_NONE}, 0);
+ if (mEglSurface == EGL14.EGL_NO_SURFACE) {
+ throw new RuntimeException("eglCreatePbufferSurface failed");
+ }
+
+ mEglContext = EGL14.eglCreateContext(mEglDisplay, mEglConfig, EGL14.EGL_NO_CONTEXT,
+ new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE}, 0);
+ if (mEglContext == EGL14.EGL_NO_CONTEXT) {
+ throw new RuntimeException("eglCreateContext failed");
+ }
+
+ if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+ throw new RuntimeException("eglMakeCurrent failed");
+ }
+ }
+
+ @After
+ public void tearDown() throws Throwable {
+ if (mReader != null) {
+ mReader.close();
+ mReader = null;
+ }
+ if (mWriter != null) {
+ mWriter.close();
+ mWriter = null;
+ }
+ if (mSurface != null) {
+ mSurface.release();
+ mSurface = null;
+ }
+ if (mSurfaceTexture != null) {
+ mSurfaceTexture.release();
+ mSurfaceTexture = null;
+ glDeleteTextures(1, mTex, 0);
+ }
+ if (mEglDisplay != EGL14.EGL_NO_DISPLAY) {
+ EGL14.eglDestroyContext(mEglDisplay, mEglContext);
+ EGL14.eglDestroySurface(mEglDisplay, mEglSurface);
+ EGL14.eglTerminate(mEglDisplay);
+ }
+ mEglDisplay = EGL14.EGL_NO_DISPLAY;
+ mEglContext = EGL14.EGL_NO_CONTEXT;
+ mEglSurface = EGL14.EGL_NO_SURFACE;
+ }
+
+ @UiThreadTest
+ @Test
+ public void getDataSpace() {
+ mTex = new int[1];
+ glGenTextures(1, mTex, 0);
+
+ // create a surfaceTexture attached to mTex[0]
+ mSurfaceTexture = new SurfaceTexture(mTex[0]);
+ mSurfaceTexture.setDefaultBufferSize(16, 16);
+
+ mSurface = new Surface(mSurfaceTexture);
+ mWriter = ImageWriter.newInstance(mSurface, 1);
+
+ long dataSpace = DataSpace.pack(DataSpace.STANDARD_BT709,
+ DataSpace.TRANSFER_SMPTE_170M,
+ DataSpace.RANGE_LIMITED);
+ Image inputImage = null;
+ try {
+ inputImage = mWriter.dequeueInputImage();
+ inputImage.setDataSpace(dataSpace);
+ assertEquals(dataSpace, inputImage.getDataSpace());
+
+ mWriter.queueInputImage(inputImage);
+
+ mSurfaceTexture.updateTexImage();
+ long outDataSpace = mSurfaceTexture.getDataSpace();
+
+ assertEquals(dataSpace, outDataSpace);
+ assertEquals(DataSpace.STANDARD_BT709, DataSpace.getStandard(outDataSpace));
+ assertEquals(DataSpace.TRANSFER_SMPTE_170M, DataSpace.getTransfer(outDataSpace));
+ assertEquals(DataSpace.RANGE_LIMITED, DataSpace.getRange(outDataSpace));
+ } finally {
+ if (inputImage != null) {
+ inputImage.close();
+ inputImage = null;
+ }
+ }
+ }
+
+ @UiThreadTest
+ @Test
+ public void getDataSpaceWithoutSetDataSpace() {
+ mTex = new int[1];
+ glGenTextures(1, mTex, 0);
+
+ // create a surfaceTexture attached to mTex[0]
+ mSurfaceTexture = new SurfaceTexture(mTex[0]);
+ mSurfaceTexture.setDefaultBufferSize(16, 16);
+
+ mSurface = new Surface(mSurfaceTexture);
+ mWriter = ImageWriter.newInstance(mSurface, 1);
+
+ Image inputImage = null;
+ try {
+ inputImage = mWriter.dequeueInputImage();
+ mWriter.queueInputImage(inputImage);
+
+ mSurfaceTexture.updateTexImage();
+
+ assertEquals(DataSpace.DATASPACE_UNKNOWN, mSurfaceTexture.getDataSpace());
+ } finally {
+ if (inputImage != null) {
+ inputImage.close();
+ inputImage = null;
+ }
+ }
+ }
+
+ @UiThreadTest
+ @Test
+ public void getDataSpaceWithFormatYUV420_888() {
+ mTex = new int[1];
+ glGenTextures(1, mTex, 0);
+
+ // create a surfaceTexture attached to mTex[0]
+ mSurfaceTexture = new SurfaceTexture(mTex[0]);
+ mSurfaceTexture.setDefaultBufferSize(16, 16);
+
+ mSurface = new Surface(mSurfaceTexture);
+ mWriter = ImageWriter.newInstance(mSurface, 1, ImageFormat.YUV_420_888);
+
+ Image inputImage = null;
+ try {
+ inputImage = mWriter.dequeueInputImage();
+ mWriter.queueInputImage(inputImage);
+
+ mSurfaceTexture.updateTexImage();
+
+ // test default dataspace value of ImageFormat.YUV_420_888 format.
+ assertEquals(DataSpace.DATASPACE_JFIF, mSurfaceTexture.getDataSpace());
+ } finally {
+ if (inputImage != null) {
+ inputImage.close();
+ inputImage = null;
+ }
+ }
+ }
+
+ @UiThreadTest
+ @Test
+ public void getDataSpaceFromImageReaderNextImage() {
+ mReader = ImageReader.newInstance(100, 100, ImageFormat.YUV_420_888, 1);
+ mWriter = ImageWriter.newInstance(mReader.getSurface(), 1);
+
+ long dataSpace = DataSpace.pack(DataSpace.STANDARD_BT601_625,
+ DataSpace.TRANSFER_SMPTE_170M,
+ DataSpace.RANGE_FULL);
+
+ Image outputImage = null;
+ Image nextImage = null;
+ try {
+ outputImage = mWriter.dequeueInputImage();
+ outputImage.setDataSpace(dataSpace);
+
+ mWriter.queueInputImage(outputImage);
+
+ nextImage = mReader.acquireLatestImage();
+ assertEquals(dataSpace, nextImage.getDataSpace());
+ } finally {
+ if (outputImage != null) {
+ outputImage.close();
+ outputImage = null;
+ }
+ if (nextImage != null) {
+ nextImage.close();
+ nextImage = null;
+ }
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/hdmi/cts/HdmiControlManagerTest.java b/tests/tests/hardware/src/android/hardware/hdmi/cts/HdmiControlManagerTest.java
index 9c0dfae..f74e666 100644
--- a/tests/tests/hardware/src/android/hardware/hdmi/cts/HdmiControlManagerTest.java
+++ b/tests/tests/hardware/src/android/hardware/hdmi/cts/HdmiControlManagerTest.java
@@ -44,8 +44,11 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -321,6 +324,27 @@
}
@Test
+ public void testHdmiCecConfig_RoutingControl() throws Exception {
+ // Save original value
+ int originalValue = mHdmiControlManager.getRoutingControl();
+ if (!mHdmiControlManager.getUserCecSettings().contains(
+ HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL)) {
+ return;
+ }
+ try {
+ for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(
+ HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL)) {
+ mHdmiControlManager.setRoutingControl(value);
+ assertThat(mHdmiControlManager.getRoutingControl()).isEqualTo(value);
+ }
+ } finally {
+ // Restore original value
+ mHdmiControlManager.setRoutingControl(originalValue);
+ assertThat(mHdmiControlManager.getRoutingControl()).isEqualTo(originalValue);
+ }
+ }
+
+ @Test
public void testHdmiCecConfig_HdmiCecVolumeControlEnabled() throws Exception {
// Save original value
int originalValue = mHdmiControlManager.getHdmiCecVolumeControlEnabled();
@@ -387,6 +411,27 @@
}
@Test
+ public void testHdmiCecConfig_SystemAudioControl() throws Exception {
+ // Save original value
+ int originalValue = mHdmiControlManager.getSystemAudioControl();
+ if (!mHdmiControlManager.getUserCecSettings().contains(
+ HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL)) {
+ return;
+ }
+ try {
+ for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(
+ HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL)) {
+ mHdmiControlManager.setSystemAudioControl(value);
+ assertThat(mHdmiControlManager.getSystemAudioControl()).isEqualTo(value);
+ }
+ } finally {
+ // Restore original value
+ mHdmiControlManager.setSystemAudioControl(originalValue);
+ assertThat(mHdmiControlManager.getSystemAudioControl()).isEqualTo(originalValue);
+ }
+ }
+
+ @Test
public void testHdmiCecConfig_SystemAudioModeMuting() throws Exception {
// Save original value
int originalValue = mHdmiControlManager.getSystemAudioModeMuting();
@@ -450,4 +495,59 @@
originalValue);
}
}
+
+ @Test
+ public void testHdmiCecConfig_SadsToQuery() throws Exception {
+ List<String> settings = new ArrayList<String>(Arrays.asList(
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_LPCM,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DD,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG1,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MP3,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG2,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_AAC,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTS,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ATRAC,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DDP,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTSHD,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_TRUEHD,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DST,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_WMAPRO,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX));
+ // User-configurable settings
+ List<String> userConfigurableSettings = new ArrayList<>();
+ // Map from user-configurable settings to original values
+ Map<String, Integer> originalValues = new HashMap<>();
+ for (String setting : settings) {
+ if (mHdmiControlManager.getUserCecSettings().contains(setting)) {
+ userConfigurableSettings.add(setting);
+ originalValues.put(setting, mHdmiControlManager.getSadPresenceInQuery(setting));
+ }
+ }
+ if (userConfigurableSettings.size() == 0) {
+ return;
+ }
+ try {
+ for (String setting : userConfigurableSettings) {
+ for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(setting)) {
+ mHdmiControlManager.setSadsPresenceInQuery(
+ new ArrayList<String>(Arrays.asList(setting)), value);
+ assertThat(mHdmiControlManager.getSadPresenceInQuery(setting)).isEqualTo(value);
+ }
+ }
+ for (String setting : userConfigurableSettings) {
+ for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(setting)) {
+ mHdmiControlManager.setSadPresenceInQuery(setting, value);
+ assertThat(mHdmiControlManager.getSadPresenceInQuery(setting)).isEqualTo(value);
+ }
+ }
+ } finally {
+ // Restore original values
+ for (String setting : originalValues.keySet()) {
+ mHdmiControlManager.setSadPresenceInQuery(setting, originalValues.get(setting));
+ assertThat(mHdmiControlManager.getSadPresenceInQuery(setting)).isEqualTo(
+ originalValues.get(setting));
+ }
+ }
+ }
}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java
index 1afad38..4200bc4 100755
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java
@@ -16,7 +16,6 @@
package android.hardware.input.cts.tests;
-import android.content.Intent;
import android.hardware.cts.R;
import android.server.wm.WindowManagerStateHelper;
@@ -49,11 +48,6 @@
public void testHomeKey() {
testInputEvents(R.raw.google_atvreferenceremote_homekey);
WindowManagerStateHelper wmStateHelper = new WindowManagerStateHelper();
- Intent intent = new Intent();
- intent.addCategory(Intent.CATEGORY_HOME);
- intent.setAction(Intent.ACTION_MAIN);
- mActivityRule.getActivity().startActivity(intent);
-
wmStateHelper.waitForHomeActivityVisible();
wmStateHelper.assertHomeActivityVisible(true);
}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/GooglePixelUsbCEarbudsTest.kt b/tests/tests/hardware/src/android/hardware/input/cts/tests/GooglePixelUsbCEarbudsTest.kt
new file mode 100644
index 0000000..5672b81
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/GooglePixelUsbCEarbudsTest.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input.cts.tests
+
+import android.hardware.cts.R
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+public class GooglePixelUsbCEarbudsTest : InputHidTestCase(R.raw.google_pixelusbcearbuds_register) {
+
+ @Test
+ fun testAllKeys() {
+ testInputEvents(R.raw.google_pixelusbcearbuds_keyeventtests)
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
index 209b912..d19fb73 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
@@ -126,7 +126,9 @@
assertEquals(mCurrentTestCase + " (action)",
expectedKeyEvent.getAction(), receivedKeyEvent.getAction());
assertSource(mCurrentTestCase, expectedKeyEvent, receivedKeyEvent);
- assertEquals(mCurrentTestCase + " (keycode)",
+ assertEquals(mCurrentTestCase + " (keycode) expected: "
+ + KeyEvent.keyCodeToString(expectedKeyEvent.getKeyCode()) + " received: "
+ + KeyEvent.keyCodeToString(receivedKeyEvent.getKeyCode()),
expectedKeyEvent.getKeyCode(), receivedKeyEvent.getKeyCode());
assertMetaState(mCurrentTestCase, expectedKeyEvent.getMetaState(),
receivedKeyEvent.getMetaState());
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputUinputTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputUinputTestCase.java
deleted file mode 100644
index ec4e384..0000000
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputUinputTestCase.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.input.cts.tests;
-
-import com.android.cts.input.UinputDevice;
-import com.android.cts.input.UinputTestData;
-
-import java.util.List;
-
-public class InputUinputTestCase extends InputTestCase {
- private static final String TAG = "InputUinputTestCase";
- private UinputDevice mUinputDevice;
-
- InputUinputTestCase(int registerResourceId) {
- super(registerResourceId);
- }
-
- @Override
- protected void setUpDevice(int id, int vendorId, int productId, int sources,
- String registerCommand) {
- mUinputDevice = new UinputDevice(mInstrumentation, id, vendorId, productId, sources,
- registerCommand);
- }
-
- @Override
- protected void tearDownDevice() {
- if (mUinputDevice != null) {
- mUinputDevice.close();
- }
- }
-
- @Override
- protected void testInputDeviceEvents(int resourceId) {
- List<UinputTestData> tests = mParser.getUinputTestData(resourceId);
-
- for (UinputTestData testData: tests) {
- mCurrentTestCase = testData.name;
-
- // Send all of the evdev Events
- for (int i = 0; i < testData.evdevEvents.size(); i++) {
- final String injections = testData.evdevEvents.get(i);
- mUinputDevice.injectEvents(injections);
- }
- verifyEvents(testData.events);
- }
- }
-}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerRaijuMobileBluetoothTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerRaijuMobileBluetoothTest.java
index d27c96c..a55c3b6 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerRaijuMobileBluetoothTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerRaijuMobileBluetoothTest.java
@@ -16,7 +16,6 @@
package android.hardware.input.cts.tests;
-import android.content.Intent;
import android.hardware.cts.R;
import android.server.wm.WindowManagerStateHelper;
import android.view.KeyEvent;
@@ -55,12 +54,6 @@
public void testHomeKey() throws Exception {
mActivityRule.getActivity().addUnhandleKeyCode(KeyEvent.KEYCODE_BUTTON_MODE);
testInputEvents(R.raw.razer_raiju_mobile_bluetooth_homekey);
- // Put DUT on home screen
- Intent intent = new Intent();
- intent.addCategory(Intent.CATEGORY_HOME);
- intent.setAction(Intent.ACTION_MAIN);
- mActivityRule.getActivity().startActivity(intent);
-
WindowManagerStateHelper wmStateHelper = new WindowManagerStateHelper();
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java
index 1405530..030c030 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java
@@ -16,7 +16,6 @@
package android.hardware.input.cts.tests;
-import android.content.Intent;
import android.hardware.cts.R;
import android.server.wm.WindowManagerStateHelper;
@@ -55,12 +54,6 @@
public void testHomeKey() {
testInputEvents(R.raw.razer_serval_homekey);
WindowManagerStateHelper wmStateHelper = new WindowManagerStateHelper();
- // Put DUT on home screen
- Intent intent = new Intent();
- intent.addCategory(Intent.CATEGORY_HOME);
- intent.setAction(Intent.ACTION_MAIN);
- mActivityRule.getActivity().startActivity(intent);
-
wmStateHelper.waitForHomeActivityVisible();
wmStateHelper.assertHomeActivityVisible(true);
diff --git a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
index 94dd585..e6de053 100644
--- a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
@@ -16,6 +16,8 @@
package android.keystore.cts;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -42,6 +44,9 @@
import com.google.common.collect.ObjectArrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.security.AlgorithmParameters;
@@ -49,10 +54,10 @@
import java.security.Key;
import java.security.KeyStoreException;
import java.security.Provider;
+import java.security.Provider.Service;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.MGF1ParameterSpec;
-import java.security.Provider.Service;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
@@ -74,9 +79,6 @@
import javax.crypto.spec.PSource;
import javax.crypto.spec.SecretKeySpec;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
/**
* Tests for algorithm-agnostic functionality of {@code Cipher} implementations backed by Android
* Keystore.
@@ -1205,6 +1207,7 @@
fail("Importing auth bound keys to an insecure device should fail");
} catch (KeyStoreException e) {
// Expected behavior
+ assertThat(e.getCause()).isInstanceOf(IllegalStateException.class);
}
}
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/ImportWrappedKeyTest.java b/tests/tests/keystore/src/android/keystore/cts/ImportWrappedKeyTest.java
index 4b66992..e61d2b3 100644
--- a/tests/tests/keystore/src/android/keystore/cts/ImportWrappedKeyTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/ImportWrappedKeyTest.java
@@ -25,16 +25,16 @@
import static android.security.keymaster.KeymasterDefs.KM_PAD_PKCS7;
import static android.security.keymaster.KeymasterDefs.KM_PURPOSE_DECRYPT;
import static android.security.keymaster.KeymasterDefs.KM_PURPOSE_ENCRYPT;
-import static android.security.keystore.KeyProperties.PURPOSE_WRAP_KEY;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.keystore.cts.util.TestUtils;
-import android.os.SystemProperties;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.SecureKeyImportUnavailableException;
@@ -52,6 +52,8 @@
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.DERTaggedObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.security.Key;
import java.security.KeyPair;
@@ -63,7 +65,6 @@
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.MGF1ParameterSpec;
-import java.security.spec.RSAKeyGenParameterSpec;
import java.util.Arrays;
import javax.crypto.Cipher;
@@ -74,16 +75,6 @@
import javax.crypto.spec.PSource;
import javax.crypto.spec.SecretKeySpec;
-import java.lang.Process;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.lang.InterruptedException;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
@RunWith(AndroidJUnit4.class)
public class ImportWrappedKeyTest {
private static final String TAG = "ImportWrappedKeyTest";
@@ -146,20 +137,24 @@
@Test
public void testKeyStore_ImportWrappedKeyWrappingKeyMissing() throws Exception {
final String EXPECTED_FAILURE = "Failed to import wrapped key. Keystore error code: 7";
- String failureMessage = null;
+ KeyStoreException exception = null;
try {
byte [] fakeWrappedKey = new byte[1];
importWrappedKey(fakeWrappedKey, WRAPPING_KEY_ALIAS + "_Missing");
} catch (KeyStoreException e) {
- failureMessage = e.getMessage();
+ exception = e;
+
}
- if (failureMessage == null) {
- fail("Did not hit a failure but expected one");
- }
+ assertWithMessage("Did not hit a failure but expected one").that(exception).isNotNull();
- assertEquals(failureMessage, EXPECTED_FAILURE);
+ assertThat(exception.getMessage()).isEqualTo(EXPECTED_FAILURE);
+ assertThat(exception.getCause()).isInstanceOf(android.security.KeyStoreException.class);
+ android.security.KeyStoreException ksException =
+ (android.security.KeyStoreException) exception.getCause();
+ assertThat(ksException.getNumericErrorCode()).isEqualTo(
+ android.security.KeyStoreException.ERROR_KEY_DOES_NOT_EXIST);
}
@Test
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
index 3f83027..33b22ec 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
@@ -46,7 +46,6 @@
import static android.security.keystore.KeyProperties.SIGNATURE_PADDING_RSA_PKCS1;
import static android.security.keystore.KeyProperties.SIGNATURE_PADDING_RSA_PSS;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.either;
@@ -54,7 +53,6 @@
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -77,18 +75,22 @@
import android.security.keystore.KeyProperties;
import android.util.ArraySet;
import android.util.Log;
+
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.RequiresDevice;
import androidx.test.runner.AndroidJUnit4;
import com.google.common.collect.ImmutableSet;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.File;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
@@ -106,14 +108,9 @@
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+
import javax.crypto.KeyGenerator;
-import org.bouncycastle.asn1.x500.X500Name;
-import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
/**
* Tests for Android KeysStore attestation.
*/
@@ -211,10 +208,11 @@
curves[curveIndex], keySizes[curveIndex],
purposes[purposeIndex], devicePropertiesAttestation);
} catch (Throwable e) {
- if (devicePropertiesAttestation
- && (e.getCause() instanceof KeyStoreException)
- && KM_ERROR_CANNOT_ATTEST_IDS ==
- ((KeyStoreException) e.getCause()).getErrorCode()) {
+ boolean isIdAttestationFailure =
+ (e.getCause() instanceof KeyStoreException)
+ && KeyStoreException.ERROR_ID_ATTESTATION_FAILURE
+ == ((KeyStoreException) e.getCause()).getNumericErrorCode();
+ if (devicePropertiesAttestation && isIdAttestationFailure) {
Log.i(TAG, "key attestation with device IDs not supported; "
+ "test skipped");
continue;
@@ -232,6 +230,19 @@
}
}
+ private void assertPublicAttestationError(KeyStoreException keyStoreException,
+ boolean devicePropertiesAttestation) {
+ // Assert public failure information.
+ int errorCode = keyStoreException.getNumericErrorCode();
+ String assertMessage = String.format(
+ "Error code was %d, device properties attestation? %b",
+ errorCode, devicePropertiesAttestation);
+ assertTrue(assertMessage, KeyStoreException.ERROR_INCORRECT_USAGE == errorCode
+ || (devicePropertiesAttestation
+ && KeyStoreException.ERROR_ID_ATTESTATION_FAILURE == errorCode));
+ assertFalse(keyStoreException.isTransientFailure());
+ }
+
@Test
public void testEcAttestation_TooLargeChallenge() throws Exception {
if (!TestUtils.isAttestationSupported()) {
@@ -250,6 +261,7 @@
(devicePropertiesAttestation
&& KM_ERROR_CANNOT_ATTEST_IDS == cause.getErrorCode())
);
+ assertPublicAttestationError(cause, devicePropertiesAttestation);
}
}
}
@@ -452,6 +464,9 @@
// Attestation is expected to fail because of lack of permissions.
KeyStoreException cause = (KeyStoreException) e.getCause();
assertEquals(KM_ERROR_PERMISSION_DENIED, cause.getErrorCode());
+ // Assert public failure information.
+ assertEquals(KeyStoreException.ERROR_PERMISSION_DENIED, cause.getNumericErrorCode());
+ assertFalse(cause.isTransientFailure());
} finally {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
@@ -547,6 +562,7 @@
(devicePropertiesAttestation
&& KM_ERROR_CANNOT_ATTEST_IDS == cause.getErrorCode())
);
+ assertPublicAttestationError(cause, devicePropertiesAttestation);
}
}
}
@@ -685,9 +701,11 @@
testRsaAttestation(challenge, false /* includeValidityDates */, keySize, purpose,
paddings, devicePropertiesAttestation);
} catch (Throwable e) {
- if (devicePropertiesAttestation && (e.getCause() instanceof KeyStoreException)
- && KM_ERROR_CANNOT_ATTEST_IDS ==
- ((KeyStoreException) e.getCause()).getErrorCode()) {
+ boolean isIdAttestationFailure =
+ (e.getCause() instanceof KeyStoreException)
+ && KeyStoreException.ERROR_ID_ATTESTATION_FAILURE
+ == ((KeyStoreException) e.getCause()).getNumericErrorCode();
+ if (devicePropertiesAttestation && isIdAttestationFailure) {
Log.i(TAG, "key attestation with device IDs not supported; test skipped");
continue;
}
@@ -1191,7 +1209,9 @@
assertTrue("Verified boot key is only " + rootOfTrust.getVerifiedBootKey().length +
" bytes long", rootOfTrust.getVerifiedBootKey().length >= 32);
if (requireLocked) {
- assertTrue(rootOfTrust.isDeviceLocked());
+ final String unlockedDeviceMessage = "The device's bootloader must be locked. This may "
+ + "not be the default for pre-production devices.";
+ assertTrue(unlockedDeviceMessage, rootOfTrust.isDeviceLocked());
checkEntropy(rootOfTrust.getVerifiedBootKey());
assertEquals(KM_VERIFIED_BOOT_VERIFIED, rootOfTrust.getVerifiedBootState());
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyStoreExceptionTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyStoreExceptionTest.java
new file mode 100644
index 0000000..515d0e5
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyStoreExceptionTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.security.KeyStoreException;
+import android.security.keymaster.KeymasterDefs;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidJUnit4.class)
+public class KeyStoreExceptionTest {
+ @Test
+ public void testAllKeymasterDefsAreCovered() throws IllegalAccessException {
+ ImmutableList<Field> kmErrors = getKeymasterDefsFields();
+ assertTrue("Test bug: there should be errors to look up",
+ kmErrors.size() > 0);
+
+ for (Field f : kmErrors) {
+ assertTrue(String.format("Missing entry for field %s", f.getName()),
+ KeyStoreException.hasFailureInfoForError(f.getInt(null)));
+ }
+ }
+
+ public static ImmutableList<Field> getKeymasterDefsFields() {
+ ImmutableList.Builder<Field> errorFieldsBuilder = new ImmutableList.Builder<>();
+
+ Class kmDefsClass = KeymasterDefs.class;
+ for (Field f : kmDefsClass.getDeclaredFields()) {
+ if (f.getName().startsWith("KM_ERROR") && f.getType().equals(int.class)) {
+ errorFieldsBuilder.add(f);
+ }
+ }
+
+ return errorFieldsBuilder.build();
+ }
+}
diff --git a/tests/tests/libcoreapievolution/Android.bp b/tests/tests/libcoreapievolution/Android.bp
index eed4fc3..954f3a2 100644
--- a/tests/tests/libcoreapievolution/Android.bp
+++ b/tests/tests/libcoreapievolution/Android.bp
@@ -26,6 +26,8 @@
libs: ["android.test.base"],
srcs: ["src/**/*.java"],
sdk_version: "current",
+ min_sdk_version: "31",
+ target_sdk_version: "31",
// Tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/tests/tests/libcorefileio/Android.bp b/tests/tests/libcorefileio/Android.bp
index 3febb32..b9db7d2 100644
--- a/tests/tests/libcorefileio/Android.bp
+++ b/tests/tests/libcorefileio/Android.bp
@@ -26,6 +26,8 @@
libs: ["android.test.base"],
srcs: ["src/**/*.java"],
sdk_version: "current",
+ min_sdk_version: "31",
+ target_sdk_version: "31",
// Tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/tests/tests/match_flags/OWNERS b/tests/tests/match_flags/OWNERS
index 8a44fb2..d1eff73 100644
--- a/tests/tests/match_flags/OWNERS
+++ b/tests/tests/match_flags/OWNERS
@@ -2,4 +2,4 @@
patb@google.com
toddke@google.com
chiuwinson@google.com
-rtmitchell@google.com
+zyy@google.com
diff --git a/tests/tests/media/AndroidManifest.xml b/tests/tests/media/AndroidManifest.xml
index 267f456..86f744f 100644
--- a/tests/tests/media/AndroidManifest.xml
+++ b/tests/tests/media/AndroidManifest.xml
@@ -39,6 +39,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
<uses-permission android:name="android.permission.VIBRATE"/>
diff --git a/tests/tests/media/AndroidTest.xml b/tests/tests/media/AndroidTest.xml
index 23cc264..8d20655 100644
--- a/tests/tests/media/AndroidTest.xml
+++ b/tests/tests/media/AndroidTest.xml
@@ -19,7 +19,6 @@
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
- <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
<option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
<option name="set-test-harness" value="false" />
@@ -55,7 +54,6 @@
<!-- test-timeout unit is ms, value = 30 min -->
<option name="test-timeout" value="1800000" />
<option name="runtime-hint" value="4h" />
- <option name="exclude-annotation" value="org.junit.Ignore" />
<option name="hidden-api-checks" value="false" />
<!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
<option name="isolated-storage" value="false" />
diff --git a/tests/tests/media/OWNERS b/tests/tests/media/OWNERS
index 6775f87..f99958f 100644
--- a/tests/tests/media/OWNERS
+++ b/tests/tests/media/OWNERS
@@ -2,14 +2,91 @@
include ../../media/OWNERS
elaurent@google.com
etalvala@google.com
-hdmoon@google.com
hunga@google.com
-insun@google.com
-jaewan@google.com
-jinpark@google.com
jmtrivi@google.com
jsharkey@android.com
-sungsoo@google.com
-# go/android-fwk-media-solutions for info on areas of ownership.
-include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
+per-file src/android/media/cts/AdaptivePlaybackTest.java = hunga@google.com
+per-file src/android/media/cts/AudioMetadataTest.java = hunga@google.com
+per-file src/android/media/cts/AudioRecordTest.java = hunga@google.com
+per-file src/android/media/cts/AudioRecord_BufferSizeTest.java = hunga@google.com
+per-file src/android/media/cts/AudioTrackLatencyTest.java = hunga@google.com
+per-file src/android/media/cts/AudioTrackOffloadTest.java = hunga@google.com
+per-file src/android/media/cts/AudioTrackSurroundTest.java = hunga@google.com
+per-file src/android/media/cts/AudioTrackTest.java = hunga@google.com
+per-file src/android/media/cts/AudioTrack_ListenerTest.java = hunga@google.com
+per-file src/android/media/cts/SoundPoolAacTest.java = hunga@google.com
+per-file src/android/media/cts/SoundPoolHapticTest.java = hunga@google.com
+per-file src/android/media/cts/SoundPoolMidiTest.java = hunga@google.com
+per-file src/android/media/cts/SoundPoolOggTest.java = hunga@google.com
+per-file src/android/media/cts/SoundPoolTest.java = hunga@google.com
+per-file src/android/media/cts/VolumeShaperTest.java = hunga@google.com
+
+per-file src/android/media/cts/AudioAttributesTest.java = jmtrivi@google.com
+per-file src/android/media/cts/AudioFocusTest.java = jmtrivi@google.com
+per-file src/android/media/cts/AudioPlaybackConfigurationTest.java = jmtrivi@google.com
+per-file src/android/media/cts/AudioRecordingConfigurationTest.java = jmtrivi@google.com
+per-file src/android/media/cts/DecoderTestAacDrc.java = jmtrivi@google.com
+per-file src/android/media/cts/DecoderTestAacFormat.java = jmtrivi@google.com
+per-file src/android/media/cts/DecoderTestXheAac.java = jmtrivi@google.com
+per-file src/android/media/cts/LoudnessEnhancerTest.java = jmtrivi@google.com
+per-file src/android/media/cts/RingtoneManagerTest.java = jmtrivi@google.com
+per-file src/android/media/cts/RingtoneTest.java = jmtrivi@google.com
+
+per-file src/android/media/cts/AudioNativeTest.java = philburk@google.com
+per-file src/android/media/cts/AudioPlayRoutingNative.java = philburk@google.com
+per-file src/android/media/cts/AudioRecordNative.java = philburk@google.com
+per-file src/android/media/cts/AudioRecordRoutingNative.java = philburk@google.com
+per-file src/android/media/cts/AudioTrackNative.java = philburk@google.com
+per-file src/android/media/cts/MidiSoloTest.java = philburk@google.com
+
+per-file src/android/media/cts/DecodeAccuracyTest.java = lajos@google.com
+per-file src/android/media/cts/DecodeEditEncodeTest.java = lajos@google.com
+per-file src/android/media/cts/DecoderConformanceTest.java = lajos@google.com
+per-file src/android/media/cts/DecoderTest.java = lajos@google.com
+per-file src/android/media/cts/EncodeDecodeTest.java = lajos@google.com
+per-file src/android/media/cts/EncodeVirtualDisplayTest.java = lajos@google.com
+per-file src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java = lajos@google.com
+per-file src/android/media/cts/EncoderTest.java = lajos@google.com
+per-file src/android/media/cts/MediaCodecBlockModelTest.java = lajos@google.com
+per-file src/android/media/cts/MediaCodecCapabilitiesTest.java = lajos@google.com
+per-file src/android/media/cts/MediaCodecListTest.java = lajos@google.com
+per-file src/android/media/cts/MediaCodecTest.java = lajos@google.com
+per-file src/android/media/cts/NativeDecoderTest.java = lajos@google.com
+per-file src/android/media/cts/NativeImageReaderTest.java = lajos@google.com
+per-file src/android/media/cts/ResourceManagerTest.java = lajos@google.com
+per-file src/android/media/cts/NativeDecoderTest.java = lajos@google.com
+per-file src/android/media/cts/VideoCodecTest.java = lajos@google.com
+per-file src/android/media/cts/VideoDecoderPerfTest.java = lajos@google.com
+per-file src/android/media/cts/VideoDecoderRotationTest.java = lajos@google.com
+per-file src/android/media/cts/VideoEncoderTest.java = lajos@google.com
+per-file src/android/media/cts/VideoCodecTest.java = lajos@google.com
+
+per-file src/android/media/cts/MediaBrowserServiceTest.java = olly@google.com
+per-file src/android/media/cts/MediaBrowserTest.java = olly@google.com
+per-file src/android/media/cts/MediaCasTest.java = olly@google.com
+per-file src/android/media/cts/MediaCommunicationManagerTest.java = olly@google.com
+per-file src/android/media/cts/MediaController2Test.java = olly@google.com
+per-file src/android/media/cts/MediaControllerTest.java = olly@google.com
+per-file src/android/media/cts/MediaExtractorTest.java = olly@google.com
+per-file src/android/media/cts/MediaFormatTest.java = olly@google.com
+per-file src/android/media/cts/MediaMetadataRetrieverTest.java = olly@google.com
+per-file src/android/media/cts/MediaMetadataTest.java = olly@google.com
+per-file src/android/media/cts/MediaMuxerTest.java = olly@google.com
+per-file src/android/media/cts/MediaPlayerFlakyNetworkTest.java = olly@google.com
+per-file src/android/media/cts/MediaPlayerSurfaceTest.java = olly@google.com
+per-file src/android/media/cts/MediaPlayerTest.java = olly@google.com
+per-file src/android/media/cts/MediaMuxerTest.java = olly@google.com
+per-file src/android/media/cts/MediaRecorderTest.java = olly@google.com
+per-file src/android/media/cts/MediaRoute2InfoTest.java = olly@google.com
+per-file src/android/media/cts/MediaRoute2ProviderServiceTest.java = olly@google.com
+per-file src/android/media/cts/MediaRouter2Test.java = olly@google.com
+per-file src/android/media/cts/MediaRouterTest.java = olly@google.com
+per-file src/android/media/cts/MediaSession2ServiceTest.java = olly@google.com
+per-file src/android/media/cts/MediaSession2Test.java = olly@google.com
+per-file src/android/media/cts/MediaSessionManagerTest.java = olly@google.com
+per-file src/android/media/cts/MediaSessionTest.java = olly@google.com
+per-file src/android/media/cts/StreamingMediaPlayerTest.java = olly@google.com
+
+per-file src/android/media/cts/CamcorderProfileTest.java = etalvala@google.com
+per-file src/android/media/cts/CameraProfileTest.java = etalvala@google.com
diff --git a/tests/tests/media/audio/Android.bp b/tests/tests/media/audio/Android.bp
index e7f1363..749a231 100644
--- a/tests/tests/media/audio/Android.bp
+++ b/tests/tests/media/audio/Android.bp
@@ -40,9 +40,11 @@
"jni/audio-track-native.cpp",
"jni/sl-utils.cpp",
],
- include_dirs: ["frameworks/wilhelm/include",
- "frameworks/wilhelm/src/android",
- "system/core/include"],
+ include_dirs: [
+ "frameworks/wilhelm/include",
+ "frameworks/wilhelm/src/android",
+ "system/core/include",
+ ],
shared_libs: [
"libandroid",
"liblog",
@@ -86,6 +88,8 @@
resource_dirs: ["res"],
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
+ "aidl/**/*.aidl",
],
platform_apis: true,
jni_uses_sdk_apis: true,
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioAttributesTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioAttributesTest.java
index 8d8d000..6499337 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/AudioAttributesTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioAttributesTest.java
@@ -90,6 +90,15 @@
}
}
+ // Test case 4: verify the Ultrasound content APIs for AudioAttributes
+ public void testSetUltrasoundContentType() throws Exception {
+ final AudioAttributes internalContentApiAttr = new AudioAttributes.Builder()
+ .setInternalContentType(AudioAttributes.CONTENT_TYPE_ULTRASOUND)
+ .build();
+
+ assertEquals("Ultrasound by setInternalContentType doesn't match",
+ internalContentApiAttr.getContentType(), AudioAttributes.CONTENT_TYPE_ULTRASOUND);
+ }
// -----------------------------------------------------------------
// Builder tests
// ----------------------------------
@@ -208,6 +217,34 @@
}
// -----------------------------------------------------------------
+ // Deprecation tests
+ // ----------------------------------
+ private int[] DEPRECATED_USAGES = { AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
+ AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
+ AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT };
+
+ public void testDeprecationNotificationUsagesBuilder() throws Exception {
+ for (int deprecatedNotifUsage : DEPRECATED_USAGES) {
+ final AudioAttributes aa = new AudioAttributes.Builder()
+ .setUsage(deprecatedNotifUsage)
+ .build();
+ assertEquals("Deprecated notification usage value not remapped",
+ AudioAttributes.USAGE_NOTIFICATION, aa.getUsage());
+ }
+ }
+
+ public void testDeprecationNotificationUsagesCopyBuilder() throws Exception {
+ for (int deprecatedNotifUsage : DEPRECATED_USAGES) {
+ final AudioAttributes aa = new AudioAttributes.Builder()
+ .setUsage(deprecatedNotifUsage)
+ .build();
+ final AudioAttributes copy = new AudioAttributes.Builder(aa).build();
+ assertEquals("Deprecated notification usage value not remapped",
+ AudioAttributes.USAGE_NOTIFICATION, copy.getUsage());
+ }
+ }
+
+ // -----------------------------------------------------------------
// Regression tests
// ----------------------------------
// Test against regression where setLegacyStreamType() was creating a different Builder
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioDevicesForAttributesTest.kt b/tests/tests/media/audio/src/android/media/audio/cts/AudioDevicesForAttributesTest.kt
new file mode 100644
index 0000000..9e3231f
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioDevicesForAttributesTest.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts
+
+import android.content.pm.PackageManager
+import android.media.AudioAttributes
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import android.media.cts.NonMediaMainlineTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4::class)
+class AudioDevicesForAttributesTest {
+ /**
+ * Test that getAudioDevicesForAttributes reports the output audio device(s)
+ */
+ @Test
+ fun testGetAudioDevicesForAttributes() {
+ val context = InstrumentationRegistry.getInstrumentation().context
+ assumeTrue(
+ context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)
+ )
+
+ val audioManager = context.getSystemService(AudioManager::class.java)
+ val allOutDevices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
+ var hasAtLeastOneDeviceForAttributes = false
+
+ for (usage in AudioAttributes.getSdkUsages()) {
+ val audioAttributes = AudioAttributes.Builder()
+ .setUsage(usage)
+ .build()
+
+ val audioDevicesForAttributes: List<AudioDeviceInfo> =
+ audioManager.getAudioDevicesForAttributes(audioAttributes)
+
+ assertTrue(
+ "Unknown device for attributes!",
+ allOutDevices.toList().containsAll(audioDevicesForAttributes)
+ )
+
+ if (audioDevicesForAttributes.isNotEmpty()) {
+ hasAtLeastOneDeviceForAttributes = true
+ }
+ }
+
+ if (allOutDevices.isNotEmpty()) {
+ assertTrue(
+ "No device for any AudioAttributes, though output device exists.",
+ hasAtLeastOneDeviceForAttributes
+ )
+ }
+ }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioFocusTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioFocusTest.java
index f84e680..c9b107c 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/AudioFocusTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioFocusTest.java
@@ -57,6 +57,7 @@
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build();
+ private static final String TEST_CALL_ID = "fake call";
public void testInvalidAudioFocusRequestDelayNoListener() throws Exception {
AudioFocusRequest req = null;
@@ -215,10 +216,160 @@
// verify a request that is "force duck"'d still causes loss of focus because it doesn't
// come from an A11y service, and requests are from same uid
final AudioAttributes[] attributes = {ATTR_MEDIA, ATTR_A11Y};
- doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, attributes,
+ doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, attributes,
false /*no handler*/, true /* forceDucking */);
}
+ public void testAudioFocusRequestA11y() throws Exception {
+ final AudioAttributes[] attributes = {ATTR_DRIVE_DIR, ATTR_A11Y};
+ doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE, attributes,
+ false /*no handler*/, false /* forceDucking */);
+ }
+
+ /**
+ * Test delayed focus behaviors with the sequence:
+ * 1/ media requests FOCUS_GAIN
+ * 2/ (simulated) call with focus lock: media gets FOCUS_LOSS_TRANSIENT
+ * 3/ drive dir requests FOCUS_GAIN + delay OK: is delayed + media gets FOCUS_LOSS
+ * 4/ call ends: drive dir gets FOCUS_GAIN
+ * @throws Exception when failing
+ */
+ public void testAudioFocusDelayedByCall() throws Exception {
+ Log.i(TAG, "testAudioFocusDelayedByCall");
+ final AudioManager am = new AudioManager(getContext());
+ final HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ final Handler handler = new Handler(handlerThread.getLooper());
+
+ final AudioFocusRequest callFocusReq =
+ new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
+ .setLocksFocus(true).build();
+ final FocusChangeListener mediaListener = new FocusChangeListener();
+ final AudioFocusRequest mediaFocusReq =
+ new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+ .setAudioAttributes(ATTR_MEDIA)
+ .setOnAudioFocusChangeListener(mediaListener, handler)
+ .build();
+ final FocusChangeListener driveListener = new FocusChangeListener();
+ final AudioFocusRequest driveFocusReq =
+ new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+ .setAudioAttributes(ATTR_DRIVE_DIR)
+ .setAcceptsDelayedFocusGain(true)
+ .setOnAudioFocusChangeListener(driveListener, handler)
+ .build();
+
+ // for focus request/abandon test methods
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.QUERY_AUDIO_STATE);
+ try {
+ // media requests audio focus
+ int res = am.requestAudioFocus(mediaFocusReq);
+ assertEquals("media request failed", AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+ // call requests audio focus
+ am.requestAudioFocusForTest(callFocusReq, TEST_CALL_ID, 1977, Build.VERSION_CODES.S);
+ assertEquals("call request failed", AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+ // verify media lost focus with LOSS_TRANSIENT
+ Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+ assertEquals("Focus loss not dispatched to media after call start",
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT, mediaListener.getFocusChangeAndReset());
+ // drive dir requests audio focus, verify it's delayed
+ res = am.requestAudioFocus(driveFocusReq);
+ assertEquals("Focus request from drive dir. wasn't delayed",
+ AudioManager.AUDIOFOCUS_REQUEST_DELAYED, res);
+ // verify media lost focus with LOSS as it's being kicked out of the focus stack
+ Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+ assertEquals("Focus loss not dispatched to media after drive dir delayed focus",
+ AudioManager.AUDIOFOCUS_LOSS, mediaListener.getFocusChangeAndReset());
+ // end the call, verify drive dir gets focus
+ am.abandonAudioFocusForTest(callFocusReq, TEST_CALL_ID);
+ Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+ assertEquals("Focus gain not dispatched to drive dir after call",
+ AudioManager.AUDIOFOCUS_GAIN, driveListener.getFocusChangeAndReset());
+ } finally {
+ am.abandonAudioFocusForTest(callFocusReq, TEST_CALL_ID);
+ am.abandonAudioFocusRequest(driveFocusReq);
+ am.abandonAudioFocusRequest(mediaFocusReq);
+ handler.getLooper().quit();
+ handlerThread.quitSafely();
+ getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+ }
+ }
+
+ /**
+ * Test delayed focus behaviors with the sequence:
+ * 1/ media requests FOCUS_GAIN
+ * 2/ (simulated) call with focus lock: media gets FOCUS_LOSS_TRANSIENT
+ * 3/ drive dir requests AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK + delay OK: is delayed
+ * 4/ call ends: drive dir gets FOCUS_GAIN
+ * 5/ drive dir ends: media gets FOCUS_GAIN (because it was still in the stack,
+ * unlike in testAudioFocusDelayedByCall)
+ * @throws Exception when failing
+ */
+ public void testAudioFocusTransientDelayedByCall() throws Exception {
+ Log.i(TAG, "testAudioFocusDelayedByCall");
+ final AudioManager am = new AudioManager(getContext());
+ final HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ final Handler handler = new Handler(handlerThread.getLooper());
+
+ final AudioFocusRequest callFocusReq =
+ new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
+ .setLocksFocus(true).build();
+ final FocusChangeListener mediaListener = new FocusChangeListener();
+ final AudioFocusRequest mediaFocusReq =
+ new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+ .setAudioAttributes(ATTR_MEDIA)
+ .setOnAudioFocusChangeListener(mediaListener, handler)
+ .build();
+ final FocusChangeListener driveListener = new FocusChangeListener();
+ final AudioFocusRequest driveFocusReq =
+ new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
+ .setAudioAttributes(ATTR_DRIVE_DIR)
+ .setAcceptsDelayedFocusGain(true)
+ .setOnAudioFocusChangeListener(driveListener, handler)
+ .build();
+
+ // for focus request/abandon test methods
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.QUERY_AUDIO_STATE);
+ try {
+ // media requests audio focus
+ int res = am.requestAudioFocus(mediaFocusReq);
+ assertEquals("media request failed", AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+ // call requests audio focus
+ am.requestAudioFocusForTest(callFocusReq, TEST_CALL_ID, 1977, Build.VERSION_CODES.S);
+ assertEquals("call request failed", AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+ // verify media lost focus with LOSS_TRANSIENT
+ Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+ assertEquals("Focus loss not dispatched to media after call start",
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT, mediaListener.getFocusChangeAndReset());
+ // drive dir requests audio focus, verify it's delayed
+ res = am.requestAudioFocus(driveFocusReq);
+ assertEquals("Focus request from drive dir. wasn't delayed",
+ AudioManager.AUDIOFOCUS_REQUEST_DELAYED, res);
+ // end the call, verify drive dir gets focus, and media didn't get focus change
+ am.abandonAudioFocusForTest(callFocusReq, TEST_CALL_ID);
+ Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+ assertEquals("Focus gain not dispatched to drive dir after call",
+ AudioManager.AUDIOFOCUS_GAIN, driveListener.getFocusChangeAndReset());
+ assertEquals("Focus change was dispatched to media",
+ AudioManager.AUDIOFOCUS_NONE, mediaListener.getFocusChangeAndReset());
+ // end the drive dir, verify media gets focus
+ am.abandonAudioFocusRequest(driveFocusReq);
+ Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+ assertEquals("Focus gain not dispatched to media after drive dir",
+ AudioManager.AUDIOFOCUS_GAIN, mediaListener.getFocusChangeAndReset());
+ } finally {
+ am.abandonAudioFocusForTest(callFocusReq, TEST_CALL_ID);
+ am.abandonAudioFocusRequest(driveFocusReq);
+ am.abandonAudioFocusRequest(mediaFocusReq);
+ handler.getLooper().quit();
+ handlerThread.quitSafely();
+ getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+ }
+ }
/**
* Determine if automotive feature is available
* @param context context to query
@@ -438,35 +589,38 @@
*/
private void doTestTwoPlayersGainLoss(int gainType, AudioAttributes[] attributes,
boolean useHandlerInListener) throws Exception {
- doTestTwoPlayersGainLoss(gainType, attributes, useHandlerInListener,
- false /*forceDucking*/);
+ doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN, gainType, attributes,
+ useHandlerInListener, false /*forceDucking*/);
}
/**
* Same as {@link #doTestTwoPlayersGainLoss(int, AudioAttributes[], boolean)} with forceDucking
* set to false.
- * @param gainType
- * @param attributes
+ * @param gainTypeForFirstPlayer focus gain of the focus owner on bottom (== 1st focus request)
+ * @param gainTypeForSecondPlayer focus gain of the focus owner on top (== 2nd focus request)
+ * @param attributes Audio attributes for first and second player, in order.
* @param useHandlerInListener
* @param forceDucking value used for setForceDucking in request for focus requester at top of
* stack (second requester in test).
* @throws Exception
*/
- private void doTestTwoPlayersGainLoss(int gainType, AudioAttributes[] attributes,
- boolean useHandlerInListener, boolean forceDucking) throws Exception {
+ private void doTestTwoPlayersGainLoss(int gainTypeForFirstPlayer, int gainTypeForSecondPlayer,
+ AudioAttributes[] attributes, boolean useHandlerInListener,
+ boolean forceDucking) throws Exception {
final int NB_FOCUS_OWNERS = 2;
if (NB_FOCUS_OWNERS != attributes.length) {
throw new IllegalArgumentException("Invalid test: invalid number of attributes");
}
final AudioFocusRequest[] focusRequests = new AudioFocusRequest[NB_FOCUS_OWNERS];
final FocusChangeListener[] focusListeners = new FocusChangeListener[NB_FOCUS_OWNERS];
- final int[] focusGains = { AudioManager.AUDIOFOCUS_GAIN, gainType };
+ final int[] focusGains = { gainTypeForFirstPlayer, gainTypeForSecondPlayer };
int expectedLoss = 0;
- switch (gainType) {
+ switch (gainTypeForSecondPlayer) {
case AudioManager.AUDIOFOCUS_GAIN:
expectedLoss = AudioManager.AUDIOFOCUS_LOSS;
break;
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
expectedLoss = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
break;
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
@@ -521,7 +675,7 @@
focusRequests[1] = null;
Thread.sleep(TEST_TIMING_TOLERANCE_MS);
// when focus was lost because it was requested with GAIN, focus is not given back
- if (gainType != AudioManager.AUDIOFOCUS_GAIN) {
+ if (gainTypeForSecondPlayer != AudioManager.AUDIOFOCUS_GAIN) {
assertEquals("Focus gain not dispatched", AudioManager.AUDIOFOCUS_GAIN,
focusListeners[0].getFocusChangeAndReset());
} else {
@@ -581,6 +735,7 @@
@Override
public void onAudioFocusChange(int focusChange) {
+ Log.i(TAG, "onAudioFocusChange:" + focusChange + " listener:" + this);
synchronized (mLock) {
mFocusChange = focusChange;
}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java
index fa7308f..19ce174 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java
@@ -16,8 +16,6 @@
package android.media.audio.cts;
-import static org.junit.Assert.assertNotEquals;
-
import static android.media.AudioManager.ADJUST_LOWER;
import static android.media.AudioManager.ADJUST_RAISE;
import static android.media.AudioManager.ADJUST_SAME;
@@ -42,8 +40,11 @@
import static android.media.AudioManager.VIBRATE_SETTING_ONLY_SILENT;
import static android.media.AudioManager.VIBRATE_TYPE_NOTIFICATION;
import static android.media.AudioManager.VIBRATE_TYPE_RINGER;
+import static android.provider.Settings.Global.APPLY_RAMPING_RINGER;
import static android.provider.Settings.System.SOUND_EFFECTS_ENABLED;
+import static org.junit.Assert.assertNotEquals;
+
import android.Manifest;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -54,12 +55,13 @@
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.media.AudioAttributes;
+import android.media.AudioDescriptor;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioProfile;
-import android.media.AudioDescriptor;
+import android.media.AudioTrack;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.media.MicrophoneInfo;
@@ -123,6 +125,15 @@
add(AudioDescriptor.STANDARD_NONE);
add(AudioDescriptor.STANDARD_EDID);
}};
+ private static final HashMap<Integer, Integer> DIRECT_OFFLOAD_MAP = new HashMap<>() {{
+ put(AudioManager.PLAYBACK_OFFLOAD_NOT_SUPPORTED,
+ AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED);
+ put(AudioManager.PLAYBACK_OFFLOAD_SUPPORTED,
+ AudioManager.DIRECT_PLAYBACK_OFFLOAD_SUPPORTED);
+ put(AudioManager.PLAYBACK_OFFLOAD_GAPLESS_SUPPORTED,
+ AudioManager.DIRECT_PLAYBACK_OFFLOAD_GAPLESS_SUPPORTED);
+ }};
+ private static final int INVALID_DIRECT_PLAYBACK_MODE = -1;
private AudioManager mAudioManager;
private NotificationManager mNm;
private boolean mHasVibrator;
@@ -677,6 +688,33 @@
}
}
+ public void testAccessRampingRinger() {
+ boolean originalEnabledState = mAudioManager.isRampingRingerEnabled();
+ try {
+ mAudioManager.setRampingRingerEnabled(false);
+ assertFalse(mAudioManager.isRampingRingerEnabled());
+
+ mAudioManager.setRampingRingerEnabled(true);
+ assertTrue(mAudioManager.isRampingRingerEnabled());
+ } finally {
+ mAudioManager.setRampingRingerEnabled(originalEnabledState);
+ }
+ }
+
+ public void testRampingRingerSetting() {
+ boolean originalEnabledState = mAudioManager.isRampingRingerEnabled();
+ try {
+ // Deprecated public setting should still be supported and affect the setting getter.
+ Settings.Global.putInt(mContext.getContentResolver(), APPLY_RAMPING_RINGER, 0);
+ assertFalse(mAudioManager.isRampingRingerEnabled());
+
+ Settings.Global.putInt(mContext.getContentResolver(), APPLY_RAMPING_RINGER, 1);
+ assertTrue(mAudioManager.isRampingRingerEnabled());
+ } finally {
+ mAudioManager.setRampingRingerEnabled(originalEnabledState);
+ }
+ }
+
public void testVolume() throws Exception {
if (MediaUtils.check(mIsTelevision, "No volume test due to fixed/full vol devices"))
return;
@@ -1897,6 +1935,53 @@
}
}
+ public void testGetDirectPlaybackSupport() {
+ assertEquals(AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED,
+ AudioManager.getDirectPlaybackSupport(
+ new AudioFormat.Builder().build(),
+ new AudioAttributes.Builder().build()));
+ AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+ AudioAttributes attr = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .setLegacyStreamType(STREAM_MUSIC).build();
+ for (AudioDeviceInfo device : devices) {
+ for (int encoding : device.getEncodings()) {
+ for (int channelMask : device.getChannelMasks()) {
+ for (int sampleRate : device.getSampleRates()) {
+ AudioFormat format = new AudioFormat.Builder()
+ .setEncoding(encoding)
+ .setChannelMask(channelMask)
+ .setSampleRate(sampleRate).build();
+ final int directPlaybackSupport =
+ AudioManager.getDirectPlaybackSupport(format, attr);
+ assertEquals(
+ AudioTrack.isDirectPlaybackSupported(format, attr),
+ directPlaybackSupport
+ != AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED);
+ if (directPlaybackSupport == AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED) {
+ assertEquals(
+ DIRECT_OFFLOAD_MAP.getOrDefault(
+ AudioManager.getPlaybackOffloadSupport(format, attr),
+ INVALID_DIRECT_PLAYBACK_MODE).intValue(),
+ directPlaybackSupport);
+ } else if ((directPlaybackSupport
+ & AudioManager.DIRECT_PLAYBACK_OFFLOAD_SUPPORTED)
+ != AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED) {
+ // AudioManager.getPlaybackOffloadSupport can only query offload
+ // support but not other direct support like passthrough.
+ assertNotEquals(
+ AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED,
+ DIRECT_OFFLOAD_MAP.getOrDefault(
+ AudioManager.getPlaybackOffloadSupport(format, attr),
+ AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED)
+ & directPlaybackSupport);
+ }
+ }
+ }
+ }
+ }
+ }
+
private void assertStreamVolumeEquals(int stream, int expectedVolume) throws Exception {
assertStreamVolumeEquals(stream, expectedVolume,
"Unexpected stream volume for stream=" + stream);
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioNativeTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioNativeTest.java
index 1fbabbf..1061ae1 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/AudioNativeTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioNativeTest.java
@@ -26,6 +26,7 @@
import android.media.cts.AudioHelper;
import android.media.cts.NonMediaMainlineTest;
import android.os.Build;
+import android.platform.test.annotations.Presubmit;
import com.android.compatibility.common.util.ApiLevelUtil;
import com.android.compatibility.common.util.CtsAndroidTestCase;
@@ -43,6 +44,7 @@
nativeAppendixBBufferQueue();
}
+ @Presubmit
public void testAppendixBRecording() {
// better to detect presence of microphone here.
if (!hasMicrophone()) {
@@ -51,12 +53,14 @@
nativeAppendixBRecording();
}
+ @Presubmit
public void testStereo16Playback() {
assertTrue(AudioTrackNative.test(
2 /* numChannels */, 48000 /* sampleRate */, false /* useFloat */,
20 /* msecPerBuffer */, 8 /* numBuffers */));
}
+ @Presubmit
public void testStereo16Record() {
if (!hasMicrophone()) {
return;
@@ -209,6 +213,7 @@
}
}
+ @Presubmit
public void testRecordAudit() throws Exception {
if (!hasMicrophone()) {
return;
@@ -218,6 +223,7 @@
1000 /* segmentDurationMs */, 10 /* numSegments */);
}
+ @Presubmit
public void testOutputChannelMasks() {
if (!hasAudioOutput()) {
return;
@@ -242,6 +248,7 @@
}
}
+ @Presubmit
public void testInputChannelMasks() {
if (!hasMicrophone()) {
return;
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioPlaybackCaptureTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioPlaybackCaptureTest.java
index 4ab67a5..9171312 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/AudioPlaybackCaptureTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioPlaybackCaptureTest.java
@@ -275,6 +275,7 @@
private static final boolean EXPECT_DATA = true;
private static final boolean EXPECT_SILENCE = false;
+ // We have explicit tests per usage testCaptureMatchingAllowedUsage*
private static final @AttributeUsage int[] ALLOWED_USAGES = new int[]{
AudioAttributes.USAGE_UNKNOWN,
AudioAttributes.USAGE_MEDIA,
@@ -323,17 +324,30 @@
testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
}
+ // Allowed usage tests done individually to isolate failure and keep test duration < 30s.
@Test
- public void testCaptureMatchingAllowedUsage() throws Exception {
- for (int usage : ALLOWED_USAGES) {
- mAPCTestConfig.matchingUsages = new int[]{ usage };
- testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
- testPlaybackCapture(OPT_OUT, usage, EXPECT_SILENCE);
+ public void testCaptureMatchingAllowedUsageUnknown() throws Exception {
+ doTestCaptureMatchingAllowedUsage(AudioAttributes.USAGE_UNKNOWN);
+ }
- mAPCTestConfig.matchingUsages = ALLOWED_USAGES;
- testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
- testPlaybackCapture(OPT_OUT, usage, EXPECT_SILENCE);
- }
+ @Test
+ public void testCaptureMatchingAllowedUsageMedia() throws Exception {
+ doTestCaptureMatchingAllowedUsage(AudioAttributes.USAGE_MEDIA);
+ }
+
+ @Test
+ public void testCaptureMatchingAllowedUsageGame() throws Exception {
+ doTestCaptureMatchingAllowedUsage(AudioAttributes.USAGE_GAME);
+ }
+
+ private void doTestCaptureMatchingAllowedUsage(int usage) throws Exception {
+ mAPCTestConfig.matchingUsages = new int[]{ usage };
+ testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
+ testPlaybackCapture(OPT_OUT, usage, EXPECT_SILENCE);
+
+ mAPCTestConfig.matchingUsages = ALLOWED_USAGES;
+ testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
+ testPlaybackCapture(OPT_OUT, usage, EXPECT_SILENCE);
}
@Test
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordTest.java
index ee5f0da..95fbc81 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordTest.java
@@ -73,7 +73,7 @@
import java.util.ArrayList;
import java.util.concurrent.Executor;
import java.util.List;
-
+import java.util.function.BiFunction;
@NonMediaMainlineTest
@RunWith(AndroidJUnit4.class)
@@ -180,7 +180,7 @@
return;
}
final int SLEEP_TIME = 10;
- final int RECORD_TIME = 10000;
+ final int RECORD_TIME = 5000;
assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState());
int markerInFrames = mAudioRecord.getSampleRate() / 2;
@@ -1009,7 +1009,7 @@
boolean useByteBuffer, boolean blocking,
final boolean auditRecording, final boolean isChannelIndex,
final int TEST_SR, final int TEST_CONF, final int TEST_FORMAT) throws Exception {
- final int TEST_TIME_MS = auditRecording ? 60000 : 2000;
+ final int TEST_TIME_MS = auditRecording ? 10000 : 2000;
doTest(reportName, localRecord, customHandler, periodsPerSecond, markerPeriodsPerSecond,
useByteBuffer, blocking, auditRecording, isChannelIndex,
TEST_SR, TEST_CONF, TEST_FORMAT, TEST_TIME_MS);
@@ -1099,6 +1099,7 @@
AudioTimestamp startTs = new AudioTimestamp();
assertEquals(AudioRecord.ERROR_INVALID_OPERATION,
record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC));
+ assertEquals("invalid getTimestamp doesn't affect nanoTime", 0, startTs.nanoTime);
listener.start(TEST_SR);
record.startRecording();
@@ -1118,17 +1119,19 @@
final int BUFFER_SAMPLES = BUFFER_FRAMES * numChannels;
// TODO: verify behavior when buffer size is not a multiple of frame size.
- int startTimeAtFrame = 0;
int samplesRead = 0;
+ // abstract out the buffer type used with lambda.
+ final byte[] byteData = new byte[BUFFER_SAMPLES];
+ final short[] shortData = new short[BUFFER_SAMPLES];
+ final float[] floatData = new float[BUFFER_SAMPLES];
+ final ByteBuffer byteBuffer =
+ ByteBuffer.allocateDirect(BUFFER_SAMPLES * bytesPerSample);
+ BiFunction<Integer, Boolean, Integer> reader = null;
+
+ // depending on the options, create a lambda to read data.
if (useByteBuffer) {
- ByteBuffer byteBuffer =
- ByteBuffer.allocateDirect(BUFFER_SAMPLES * bytesPerSample);
- while (samplesRead < targetSamples) {
- // the first time through, we read a single frame.
- // this sets the recording anchor position.
- int amount = samplesRead == 0 ? numChannels :
- Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
- amount *= bytesPerSample; // in bytes
+ reader = (samples, blockForData) -> {
+ final int amount = samples * bytesPerSample; // in bytes
// read always places data at the start of the byte buffer with
// position and limit are ignored. test this by setting
// position and limit to arbitrary values here.
@@ -1136,112 +1139,58 @@
final int lastLimit = 13;
byteBuffer.position(lastPosition);
byteBuffer.limit(lastLimit);
- int ret = blocking ? record.read(byteBuffer, amount) :
- record.read(byteBuffer, amount, AudioRecord.READ_NON_BLOCKING);
- // so long as amount requested in bytes is a multiple of the frame size
- // we expect the byte buffer request to be filled. Caution: the
- // byte buffer data will be in native endian order, not Java order.
- if (blocking) {
- assertEquals(amount, ret);
- } else {
- assertTrue("0 <= " + ret + " <= " + amount,
- 0 <= ret && ret <= amount);
- }
- // position, limit are not changed by read().
- assertEquals(lastPosition, byteBuffer.position());
- assertEquals(lastLimit, byteBuffer.limit());
- if (samplesRead == 0 && ret > 0) {
- firstSampleTime = System.currentTimeMillis();
- }
- samplesRead += ret / bytesPerSample;
- if (startTimeAtFrame == 0 && ret > 0 &&
- record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC) ==
- AudioRecord.SUCCESS) {
- startTimeAtFrame = samplesRead / numChannels;
- }
- }
+ final int ret = blockForData ? record.read(byteBuffer, amount) :
+ record.read(byteBuffer, amount, AudioRecord.READ_NON_BLOCKING);
+ return ret / bytesPerSample;
+ };
} else {
switch (TEST_FORMAT) {
- case AudioFormat.ENCODING_PCM_8BIT: {
- // For 8 bit data, use bytes
- byte[] byteData = new byte[BUFFER_SAMPLES];
- while (samplesRead < targetSamples) {
- // the first time through, we read a single frame.
- // this sets the recording anchor position.
- int amount = samplesRead == 0 ? numChannels :
- Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
- int ret = blocking ? record.read(byteData, 0, amount) :
- record.read(byteData, 0, amount, AudioRecord.READ_NON_BLOCKING);
- if (blocking) {
- assertEquals(amount, ret);
- } else {
- assertTrue("0 <= " + ret + " <= " + amount,
- 0 <= ret && ret <= amount);
- }
- if (samplesRead == 0 && ret > 0) {
- firstSampleTime = System.currentTimeMillis();
- }
- samplesRead += ret;
- if (startTimeAtFrame == 0 && ret > 0 &&
- record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC) ==
- AudioRecord.SUCCESS) {
- startTimeAtFrame = samplesRead / numChannels;
- }
- }
- } break;
- case AudioFormat.ENCODING_PCM_16BIT: {
- // For 16 bit data, use shorts
- short[] shortData = new short[BUFFER_SAMPLES];
- while (samplesRead < targetSamples) {
- // the first time through, we read a single frame.
- // this sets the recording anchor position.
- int amount = samplesRead == 0 ? numChannels :
- Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
- int ret = blocking ? record.read(shortData, 0, amount) :
- record.read(shortData, 0, amount, AudioRecord.READ_NON_BLOCKING);
- if (blocking) {
- assertEquals(amount, ret);
- } else {
- assertTrue("0 <= " + ret + " <= " + amount,
- 0 <= ret && ret <= amount);
- }
- if (samplesRead == 0 && ret > 0) {
- firstSampleTime = System.currentTimeMillis();
- }
- samplesRead += ret;
- if (startTimeAtFrame == 0 && ret > 0 &&
- record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC) ==
- AudioRecord.SUCCESS) {
- startTimeAtFrame = samplesRead / numChannels;
- }
- }
- } break;
- case AudioFormat.ENCODING_PCM_FLOAT: {
- float[] floatData = new float[BUFFER_SAMPLES];
- while (samplesRead < targetSamples) {
- // the first time through, we read a single frame.
- // this sets the recording anchor position.
- int amount = samplesRead == 0 ? numChannels :
- Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
- int ret = record.read(floatData, 0, amount, blocking ?
- AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
- if (blocking) {
- assertEquals(amount, ret);
- } else {
- assertTrue("0 <= " + ret + " <= " + amount,
- 0 <= ret && ret <= amount);
- }
- if (samplesRead == 0 && ret > 0) {
- firstSampleTime = System.currentTimeMillis();
- }
- samplesRead += ret;
- if (startTimeAtFrame == 0 && ret > 0 &&
- record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC) ==
- AudioRecord.SUCCESS) {
- startTimeAtFrame = samplesRead / numChannels;
- }
- }
- } break;
+ case AudioFormat.ENCODING_PCM_8BIT:
+ reader = (samples, blockForData) -> {
+ return blockForData ? record.read(byteData, 0, samples) :
+ record.read(byteData, 0, samples,
+ AudioRecord.READ_NON_BLOCKING);
+ };
+ break;
+ case AudioFormat.ENCODING_PCM_16BIT:
+ reader = (samples, blockForData) -> {
+ return blockForData ? record.read(shortData, 0, samples) :
+ record.read(shortData, 0, samples,
+ AudioRecord.READ_NON_BLOCKING);
+ };
+ break;
+ case AudioFormat.ENCODING_PCM_FLOAT:
+ reader = (samples, blockForData) -> {
+ return record.read(floatData, 0, samples,
+ blockForData ? AudioRecord.READ_BLOCKING
+ : AudioRecord.READ_NON_BLOCKING);
+ };
+ break;
+ }
+ }
+
+ while (samplesRead < targetSamples) {
+ // the first time through, we read a single frame.
+ // this sets the recording anchor position.
+ final int amount = samplesRead == 0 ? numChannels :
+ Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
+ final int ret = reader.apply(amount, blocking);
+ if (blocking) {
+ assertEquals("blocking reads should return amount requested", amount, ret);
+ } else {
+ assertTrue("non-blocking reads should return amount in range: " +
+ "0 <= " + ret + " <= " + amount,
+ 0 <= ret && ret <= amount);
+ }
+ if (samplesRead == 0 && ret > 0) {
+ firstSampleTime = System.currentTimeMillis();
+ }
+ samplesRead += ret;
+ if (startTs.nanoTime == 0 && ret > 0 &&
+ record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC)
+ == AudioRecord.SUCCESS) {
+ assertTrue("expecting valid timestamp with nonzero nanoTime",
+ startTs.nanoTime > 0);
}
}
@@ -1259,11 +1208,17 @@
Log.w(TAG, "cold input start time too long "
+ coldInputStartTime + " > 100ms");
}
- assertTrue(coldInputStartTime < 5000); // must start within 5 seconds.
+
+ final int COLD_INPUT_START_TIME_LIMIT_MS = 5000;
+ assertTrue("track must start within " + COLD_INPUT_START_TIME_LIMIT_MS + " millis",
+ coldInputStartTime < COLD_INPUT_START_TIME_LIMIT_MS);
// Verify recording completes within 50 ms of expected test time (typical 20ms)
- assertEquals(TEST_TIME_MS, endTime - firstSampleTime, auditRecording ?
- (isLowLatencyDevice() ? 1000 : 2000) : (isLowLatencyDevice() ? 50 : 400));
+ final int RECORDING_TIME_TOLERANCE_MS = auditRecording ?
+ (isLowLatencyDevice() ? 1000 : 2000) : (isLowLatencyDevice() ? 50 : 400);
+ assertEquals("recording must complete within " + RECORDING_TIME_TOLERANCE_MS
+ + " of expected test time",
+ TEST_TIME_MS, endTime - firstSampleTime, RECORDING_TIME_TOLERANCE_MS);
// Even though we've read all the frames we want, the events may not be sent to
// the listeners (events are handled through a separate internal callback thread).
@@ -1272,7 +1227,8 @@
stopRequestTime = System.currentTimeMillis();
record.stop();
- assertEquals(AudioRecord.RECORDSTATE_STOPPED, record.getRecordingState());
+ assertEquals("state should be RECORDSTATE_STOPPED after stop()",
+ AudioRecord.RECORDSTATE_STOPPED, record.getRecordingState());
stopTime = System.currentTimeMillis();
@@ -1290,10 +1246,12 @@
// get stop timestamp
AudioTimestamp stopTs = new AudioTimestamp();
- assertEquals(AudioRecord.SUCCESS,
+ assertEquals("should successfully get timestamp after stop",
+ AudioRecord.SUCCESS,
record.getTimestamp(stopTs, AudioTimestamp.TIMEBASE_MONOTONIC));
AudioTimestamp stopTsBoot = new AudioTimestamp();
- assertEquals(AudioRecord.SUCCESS,
+ assertEquals("should successfully get boottime timestamp after stop",
+ AudioRecord.SUCCESS,
record.getTimestamp(stopTsBoot, AudioTimestamp.TIMEBASE_BOOTTIME));
// printTimestamp("startTs", startTs);
@@ -1303,13 +1261,16 @@
// Log.d(TAG, "time Boottime " + SystemClock.elapsedRealtimeNanos());
// stop should not reset timestamps
- assertTrue(stopTs.framePosition >= targetFrames);
- assertEquals(stopTs.framePosition, stopTsBoot.framePosition);
- assertTrue(stopTs.nanoTime > 0);
+ assertTrue("stop timestamp position should be no less than frames read",
+ stopTs.framePosition >= targetFrames);
+ assertEquals("stop timestamp position should be same "
+ + "between monotonic and boot timestamps",
+ stopTs.framePosition, stopTsBoot.framePosition);
+ assertTrue("stop timestamp nanoTime must be set", stopTs.nanoTime > 0);
// timestamps follow a different path than data, so it is conceivable
// that first data arrives before the first timestamp is ready.
- assertTrue(startTimeAtFrame > 0); // we read a start timestamp
+ assertTrue("no start timestamp read", startTs.nanoTime > 0);
verifyContinuousTimestamps(startTs, stopTs, TEST_SR);
@@ -1324,9 +1285,7 @@
// resource needed for other tests.
record.release();
}
- if (auditRecording) { // don't check timing if auditing (messes up timing)
- return;
- }
+
final int markerPeriods = markerPeriodsPerSecond * TEST_TIME_MS / 1000;
final int updatePeriods = periodsPerSecond * TEST_TIME_MS / 1000;
final int markerPeriodsMax =
@@ -1647,6 +1606,124 @@
return;
}
+ /**
+ * Test AudioRecord Builder error handling.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testAudioRecordBuilderError() throws Exception {
+ if (!hasMicrophone()) {
+ return;
+ }
+
+ final AudioRecord[] audioRecord = new AudioRecord[1]; // pointer to AudioRecord.
+ final int BIGNUM = Integer.MAX_VALUE; // large value that should be invalid.
+ final int INVALID_SESSION_ID = 1024; // can never occur (wrong type in 3 lsbs)
+ final int INVALID_CHANNEL_MASK = -1;
+
+ try {
+ // NOTE:
+ // AudioFormat tested in AudioFormatTest#testAudioFormatBuilderError.
+
+ // We must be able to create the AudioRecord.
+ audioRecord[0] = new AudioRecord.Builder().build();
+ audioRecord[0].release();
+
+ // Out of bounds buffer size. A large size will fail in AudioRecord creation.
+ assertThrows(UnsupportedOperationException.class, () -> {
+ audioRecord[0] = new AudioRecord.Builder()
+ .setBufferSizeInBytes(BIGNUM)
+ .build();
+ });
+
+ // 0 and negative buffer size throw IllegalArgumentException
+ for (int bufferSize : new int[] {-BIGNUM, -1, 0}) {
+ assertThrows(IllegalArgumentException.class, () -> {
+ audioRecord[0] = new AudioRecord.Builder()
+ .setBufferSizeInBytes(bufferSize)
+ .build();
+ });
+ }
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ audioRecord[0] = new AudioRecord.Builder()
+ .setAudioSource(BIGNUM)
+ .build();
+ });
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ audioRecord[0] = new AudioRecord.Builder()
+ .setAudioSource(-2)
+ .build();
+ });
+
+ // Invalid session id that is positive.
+ // (logcat error message vague)
+ assertThrows(UnsupportedOperationException.class, () -> {
+ audioRecord[0] = new AudioRecord.Builder()
+ .setSessionId(INVALID_SESSION_ID)
+ .build();
+ });
+
+ // Specialty AudioRecord tests
+ assertThrows(NullPointerException.class, () -> {
+ audioRecord[0] = new AudioRecord.Builder()
+ .setAudioPlaybackCaptureConfig(null)
+ .build();
+ });
+
+ assertThrows(NullPointerException.class, () -> {
+ audioRecord[0] = new AudioRecord.Builder()
+ .setContext(null)
+ .build();
+ });
+
+ // Bad audio encoding DRA expected unsupported.
+ try {
+ audioRecord[0] = new AudioRecord.Builder()
+ .setAudioFormat(new AudioFormat.Builder()
+ .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+ .setEncoding(AudioFormat.ENCODING_DRA)
+ .build())
+ .build();
+ // Don't throw an exception, maybe it is supported somehow, but warn.
+ Log.w(TAG, "ENCODING_DRA is expected to be unsupported");
+ audioRecord[0].release();
+ audioRecord[0] = null;
+ } catch (UnsupportedOperationException e) {
+ ; // OK expected
+ }
+
+ // Sample rate out of bounds.
+ // System levels caught on AudioFormat.
+ assertThrows(IllegalArgumentException.class, () -> {
+ audioRecord[0] = new AudioRecord.Builder()
+ .setAudioFormat(new AudioFormat.Builder()
+ .setSampleRate(BIGNUM)
+ .build())
+ .build();
+ });
+
+ // Invalid channel mask
+ // This is a UOE for AudioRecord vs IAE for AudioTrack.
+ assertThrows(UnsupportedOperationException.class, () -> {
+ audioRecord[0] = new AudioRecord.Builder()
+ .setAudioFormat(new AudioFormat.Builder()
+ .setChannelMask(INVALID_CHANNEL_MASK)
+ .build())
+ .build();
+ });
+ } finally {
+ // Did we successfully complete for some reason but did not
+ // release?
+ if (audioRecord[0] != null) {
+ audioRecord[0].release();
+ audioRecord[0] = null;
+ }
+ }
+ }
+
@Test
public void testPrivacySensitiveBuilder() throws Exception {
if (!hasMicrophone()) {
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackTest.java
index bcc48a9..4575fdd 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackTest.java
@@ -1580,19 +1580,27 @@
}
@Test
- public void testPlayStaticData() throws Exception {
+ public void testPlayStaticByteArray() throws Exception {
+ doTestPlayStaticData("testPlayStaticByteArray", AudioFormat.ENCODING_PCM_8BIT);
+ }
+
+ @Test
+ public void testPlayStaticShortArray() throws Exception {
+ doTestPlayStaticData("testPlayStaticShortArray", AudioFormat.ENCODING_PCM_16BIT);
+ }
+
+ @Test
+ public void testPlayStaticFloatArray() throws Exception {
+ doTestPlayStaticData("testPlayStaticFloatArray", AudioFormat.ENCODING_PCM_FLOAT);
+ }
+
+ private void doTestPlayStaticData(String testName, int testFormat) throws Exception {
if (!hasAudioOutput()) {
Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
return;
}
// constants for test
- final String TEST_NAME = "testPlayStaticData";
- final int TEST_FORMAT_ARRAY[] = { // 6 chirps repeated (TEST_LOOPS+1) times, 3 times
- AudioFormat.ENCODING_PCM_8BIT,
- AudioFormat.ENCODING_PCM_16BIT,
- AudioFormat.ENCODING_PCM_FLOAT,
- };
final int TEST_SR_ARRAY[] = {
12055, // Note multichannel tracks will sound very short at low sample rates
48000,
@@ -1613,16 +1621,15 @@
// 200 ms less for low latency devices.
final long WAIT_TIME_MS = isLowLatencyDevice() ? WAIT_MSEC : 500;
- for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
- double frequency = 400; // frequency changes for each test
- for (int TEST_SR : TEST_SR_ARRAY) {
- for (int TEST_CONF : TEST_CONF_ARRAY) {
- playOnceStaticData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
- TEST_LOOPS, TEST_FORMAT, frequency, TEST_SR, TEST_CONF, WAIT_TIME_MS,
- TEST_LOOP_DURATION, TEST_ADDITIONAL_DRAIN_MS);
+ double frequency = 400; // frequency changes for each test
+ for (int testSampleRate : TEST_SR_ARRAY) {
+ for (int testChannelConfiguration : TEST_CONF_ARRAY) {
+ playOnceStaticData(testName, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+ TEST_LOOPS, testFormat, frequency, testSampleRate,
+ testChannelConfiguration, WAIT_TIME_MS,
+ TEST_LOOP_DURATION, TEST_ADDITIONAL_DRAIN_MS);
- frequency += 70; // increment test tone frequency
- }
+ frequency += 70; // increment test tone frequency
}
}
}
@@ -1726,14 +1733,22 @@
}
@Test
- public void testPlayStreamData() throws Exception {
+ public void testPlayStreamByteArray() throws Exception {
+ doTestPlayStreamData("testPlayStreamByteArray", AudioFormat.ENCODING_PCM_8BIT);
+ }
+
+ @Test
+ public void testPlayStreamShortArray() throws Exception {
+ doTestPlayStreamData("testPlayStreamShortArray", AudioFormat.ENCODING_PCM_16BIT);
+ }
+
+ @Test
+ public void testPlayStreamFloatArray() throws Exception {
+ doTestPlayStreamData("testPlayStreamFloatArray", AudioFormat.ENCODING_PCM_FLOAT);
+ }
+
+ private void doTestPlayStreamData(String testName, int testFormat) throws Exception {
// constants for test
- final String TEST_NAME = "testPlayStreamData";
- final int TEST_FORMAT_ARRAY[] = { // should hear 40 increasing frequency tones, 3 times
- AudioFormat.ENCODING_PCM_8BIT,
- AudioFormat.ENCODING_PCM_16BIT,
- AudioFormat.ENCODING_PCM_FLOAT,
- };
// due to downmixer algorithmic latency, source channels greater than 2 may
// sound shorter in duration at 4kHz sampling rate.
final int TEST_SR_ARRAY[] = {
@@ -1758,15 +1773,14 @@
final float TEST_SWEEP = 0; // sine wave only
final boolean TEST_IS_LOW_RAM_DEVICE = isLowRamDevice();
- for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
- double frequency = 400; // frequency changes for each test
- for (int TEST_SR : TEST_SR_ARRAY) {
- for (int TEST_CONF : TEST_CONF_ARRAY) {
- playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
- TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, frequency, TEST_SR, TEST_CONF,
- WAIT_MSEC, 0 /* mask */);
- frequency += 50; // increment test tone frequency
- }
+ double frequency = 400; // frequency changes for each test
+ for (int testSampleRate : TEST_SR_ARRAY) {
+ for (int testChannelConfiguration : TEST_CONF_ARRAY) {
+ playOnceStreamData(testName, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+ TEST_IS_LOW_RAM_DEVICE, testFormat, frequency,
+ testSampleRate, testChannelConfiguration,
+ WAIT_MSEC, 0 /* mask */);
+ frequency += 50; // increment test tone frequency
}
}
}
@@ -1794,7 +1808,7 @@
assertEquals(testName, channelCount, format.getChannelCount());
assertEquals(testName, testFormat, format.getEncoding());
// duration of test tones
- final int frames = AudioHelper.frameCountFromMsec(500 /* ms */, format);
+ final int frames = AudioHelper.frameCountFromMsec(300 /* ms */, format);
final int sourceSamples = channelCount * frames;
final double frequency = testFrequency / channelCount;
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/CallAudioInterceptionTest.java b/tests/tests/media/audio/src/android/media/audio/cts/CallAudioInterceptionTest.java
new file mode 100644
index 0000000..5c1821b
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/CallAudioInterceptionTest.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.assumeTrue;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioTrack;
+import android.platform.test.annotations.AppModeFull;
+// TODO: b/189472651 uncomment when TM version code is published
+// import android.os.Build;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+// TODO: b/189472651 uncomment when TM version code is published
+// import com.android.compatibility.common.util.ApiLevelUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Class to exercise AudioManager.getDevicesForAttributes()
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Instant apps cannot hold android.permission.CALL_AUDIO_INTERCEPTION")
+public class CallAudioInterceptionTest {
+ private static final String TAG = CallAudioInterceptionTest.class.getSimpleName();
+ private static final int SET_MODE_DELAY_MS = 300;
+
+ private AudioManager mAudioManager;
+
+ /** Test setup */
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assumeTrue(context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+
+ mAudioManager = context.getSystemService(AudioManager.class);
+ mAudioManager.setMode(AudioManager.MODE_NORMAL);
+
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.CALL_AUDIO_INTERCEPTION,
+ Manifest.permission.MODIFY_PHONE_STATE);
+ }
+
+ /** Test teardown */
+ @After
+ public void tearDown() {
+ mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ /**
+ * Test AudioManager.isPstnCallAudioInterceptable() API is implemented
+ */
+ @Test
+ public void testIsIsPstnCallAudioInterceptable() throws Exception {
+ skipIfCallredirectNotAvailable();
+
+ try {
+ boolean result = mAudioManager.isPstnCallAudioInterceptable();
+ Log.i(TAG, "isPstnCallAudioInterceptable: " + result);
+ } catch (Exception e) {
+ fail("isPstnCallAudioInterceptable() exception: " + e);
+ }
+ }
+
+ /**
+ * Test AudioManager.getCallUplinkInjectionAudioTrack() fails when success conditions are
+ * not met.
+ */
+ @Test
+ public void testGetCallUplinkInjectionAudioTrackFail() throws Exception {
+ skipIfCallredirectNotAvailable();
+
+ try {
+ AudioTrack track = mAudioManager.getCallUplinkInjectionAudioTrack(null);
+ fail("getCallUplinkInjectionAudioTrack should throw NullPointerException");
+ } catch (NullPointerException e) {
+ }
+ AudioFormat format = new AudioFormat.Builder().setSampleRate(16000)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build();
+ setAudioMode(AudioManager.MODE_NORMAL);
+ try {
+ AudioTrack track = mAudioManager.getCallUplinkInjectionAudioTrack(format);
+ fail("getCallUplinkInjectionAudioTrack should throw IllegalStateException "
+ + "in mode NORMAL");
+ } catch (IllegalStateException e) {
+ }
+
+ if (setAudioMode(AudioManager.MODE_IN_CALL)) {
+ try {
+ AudioTrack track = mAudioManager.getCallUplinkInjectionAudioTrack(format);
+ if (!mAudioManager.isPstnCallAudioInterceptable()) {
+ fail("getCallUplinkInjectionAudioTrack should throw"
+ + "UnsupportedOperationException in mode IN_CALL");
+ }
+ } catch (UnsupportedOperationException e) {
+ if (mAudioManager.isPstnCallAudioInterceptable()) {
+ fail("getCallUplinkInjectionAudioTrack should not throw"
+ + "UnsupportedOperationException in mode IN_CALL");
+ }
+ }
+ } else {
+ Log.i(TAG, "Cannot set mode to MODE_IN_CALL");
+ }
+
+ if (setAudioMode(AudioManager.MODE_IN_COMMUNICATION)) {
+ try {
+ format = new AudioFormat.Builder().setSampleRate(96000)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build();
+ AudioTrack track = mAudioManager.getCallUplinkInjectionAudioTrack(format);
+ fail("getCallUplinkInjectionAudioTrack should throw"
+ + "UnsupportedOperationException for 96000Hz");
+ format = new AudioFormat.Builder().setSampleRate(16000)
+ .setEncoding(AudioFormat.ENCODING_MP3)
+ .setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build();
+ track = mAudioManager.getCallUplinkInjectionAudioTrack(format);
+ fail("getCallUplinkInjectionAudioTrack should throw"
+ + "UnsupportedOperationException for MP3 encoding");
+ format = new AudioFormat.Builder().setSampleRate(16000)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1).build();
+ track = mAudioManager.getCallUplinkInjectionAudioTrack(format);
+ fail("getCallUplinkInjectionAudioTrack should throw"
+ + "UnsupportedOperationException for 5.1 channels");
+ } catch (UnsupportedOperationException e) {
+ }
+ } else {
+ Log.i(TAG, "Cannot set mode to MODE_IN_COMMUNICATION");
+ }
+ }
+
+ /**
+ * Test AudioManager.getCallUplinkInjectionAudioTrack() succeeds when success conditions are
+ * met.
+ */
+ @Test
+ public void testGetCallUplinkInjectionAudioTrackSuccess() throws Exception {
+ skipIfCallredirectNotAvailable();
+
+ AudioFormat format = new AudioFormat.Builder().setSampleRate(16000)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build();
+ final int[] TEST_MODES = new int[] { AudioManager.MODE_IN_CALL,
+ AudioManager.MODE_IN_COMMUNICATION,
+ AudioManager.MODE_CALL_REDIRECT,
+ AudioManager.MODE_COMMUNICATION_REDIRECT
+ };
+
+ for (int mode : TEST_MODES) {
+ if (setAudioMode(mode)) {
+ if ((mode != AudioManager.MODE_IN_CALL && mode != AudioManager.MODE_CALL_REDIRECT)
+ || mAudioManager.isPstnCallAudioInterceptable()) {
+ try {
+ Log.i(TAG, "testing mode: " + mode);
+ AudioTrack track = mAudioManager.getCallUplinkInjectionAudioTrack(format);
+ } catch (Exception e) {
+ fail("getCallUplinkInjectionAudioTrack should not throw " + e);
+ }
+ }
+ } else {
+ Log.i(TAG, "Cannot set mode to: " + mode);
+ }
+ }
+ }
+
+ /**
+ * Test AudioManager.getCallDownlinkExtractionAudioRecord() fails when success conditions are
+ * not met.
+ */
+ @Test
+ public void testGetCallDownlinkExtractionAudioRecordFail() throws Exception {
+ skipIfCallredirectNotAvailable();
+
+ try {
+ AudioRecord record = mAudioManager.getCallDownlinkExtractionAudioRecord(null);
+ fail("getCallDownlinkExtractionAudioRecord should throw NullPointerException");
+ } catch (NullPointerException e) {
+ }
+ AudioFormat format = new AudioFormat.Builder().setSampleRate(16000)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build();
+ setAudioMode(AudioManager.MODE_NORMAL);
+ try {
+ AudioRecord record = mAudioManager.getCallDownlinkExtractionAudioRecord(format);
+ fail("getCallDownlinkExtractionAudioRecord should throw IllegalStateException "
+ + "in mode NORMAL");
+ } catch (IllegalStateException e) {
+ }
+
+ if (setAudioMode(AudioManager.MODE_IN_CALL)) {
+ try {
+ AudioRecord record = mAudioManager.getCallDownlinkExtractionAudioRecord(format);
+ if (!mAudioManager.isPstnCallAudioInterceptable()) {
+ fail("getCallDownlinkExtractionAudioRecord should throw"
+ + "UnsupportedOperationException in mode IN_CALL");
+ }
+ } catch (UnsupportedOperationException e) {
+ if (mAudioManager.isPstnCallAudioInterceptable()) {
+ fail("getCallDownlinkExtractionAudioRecord should not throw"
+ + " UnsupportedOperationException in mode IN_CALL: " + e);
+ }
+ }
+ } else {
+ Log.i(TAG, "Cannot set mode to MODE_IN_CALL");
+ }
+ if (setAudioMode(AudioManager.MODE_IN_COMMUNICATION)) {
+ try {
+ format = new AudioFormat.Builder().setSampleRate(96000)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build();
+ AudioRecord record = mAudioManager.getCallDownlinkExtractionAudioRecord(format);
+ fail("getCallDownlinkExtractionAudioRecord should throw"
+ + "UnsupportedOperationException for 96000Hz");
+ format = new AudioFormat.Builder().setSampleRate(16000)
+ .setEncoding(AudioFormat.ENCODING_MP3)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build();
+ record = mAudioManager.getCallDownlinkExtractionAudioRecord(format);
+ fail("getCallDownlinkExtractionAudioRecord should throw"
+ + "UnsupportedOperationException for MP3 encoding");
+ format = new AudioFormat.Builder().setSampleRate(16000)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_IN_5POINT1).build();
+ record = mAudioManager.getCallDownlinkExtractionAudioRecord(format);
+ fail("getCallDownlinkExtractionAudioRecord should throw"
+ + "UnsupportedOperationException for 5.1 channels");
+ } catch (UnsupportedOperationException e) {
+ }
+ } else {
+ Log.i(TAG, "Cannot set mode to MODE_IN_COMMUNICATION");
+ }
+ }
+
+ /**
+ * Test AudioManager.getCallDownlinkExtractionAudioRecord() succeeds when success conditions are
+ * met.
+ */
+ @Test
+ public void testGetCallDownlinkExtractionAudioRecordSuccess() throws Exception {
+ skipIfCallredirectNotAvailable();
+
+ AudioFormat format = new AudioFormat.Builder().setSampleRate(16000)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build();
+ final int[] TEST_MODES = new int[] { AudioManager.MODE_IN_CALL,
+ AudioManager.MODE_IN_COMMUNICATION,
+ AudioManager.MODE_CALL_REDIRECT,
+ AudioManager.MODE_COMMUNICATION_REDIRECT
+ };
+
+ for (int mode : TEST_MODES) {
+ if (setAudioMode(mode)) {
+ if ((mode != AudioManager.MODE_IN_CALL && mode != AudioManager.MODE_CALL_REDIRECT)
+ || mAudioManager.isPstnCallAudioInterceptable()) {
+ try {
+ AudioRecord record =
+ mAudioManager.getCallDownlinkExtractionAudioRecord(format);
+ } catch (Exception e) {
+ fail("getCallDownlinkExtractionAudioRecord should not throw " + e);
+ }
+ }
+ } else {
+ Log.i(TAG, "Cannot set mode to: " + mode);
+ }
+ }
+ }
+
+ private boolean setAudioMode(int mode) {
+ mAudioManager.setMode(mode);
+ try {
+ Thread.sleep(SET_MODE_DELAY_MS);
+ } catch (InterruptedException e) {
+
+ }
+ return mAudioManager.getMode() == mode;
+ }
+
+ private void skipIfCallredirectNotAvailable() {
+// TODO: b/189472651 uncomment when TM version code is published
+// assumeTrue(" Call redirection not available",
+// ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU));
+ }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/MidiSoloTest.java b/tests/tests/media/audio/src/android/media/audio/cts/MidiSoloTest.java
index 95b1b78..26457d6 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/MidiSoloTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/MidiSoloTest.java
@@ -16,25 +16,20 @@
package android.media.audio.cts;
-import android.platform.test.annotations.AppModeFull;
-import java.io.IOException;
-
-
import android.content.Context;
import android.content.pm.PackageManager;
-import android.media.midi.MidiDevice;
-import android.media.midi.MidiDevice.MidiConnection;
import android.media.midi.MidiDeviceInfo;
-import android.media.midi.MidiDeviceStatus;
-import android.media.midi.MidiInputPort;
import android.media.midi.MidiManager;
import android.media.midi.MidiReceiver;
-import android.media.midi.MidiSender;
import android.os.Handler;
import android.os.Looper;
+import android.platform.test.annotations.AppModeFull;
import com.android.compatibility.common.util.CtsAndroidTestCase;
+import java.io.IOException;
+import java.util.Collection;
+
/**
* Test MIDI when there may be no MIDI devices available. There is not much we
* can test without a device.
@@ -96,12 +91,22 @@
MidiDeviceInfo[] infos = midiManager.getDevices();
assertTrue("Device list was null.", infos != null);
+ Collection<MidiDeviceInfo> legacyDeviceInfos = midiManager.getDevicesForTransport(
+ MidiManager.TRANSPORT_MIDI_BYTE_STREAM);
+ assertTrue("Legacy Device list was null.", legacyDeviceInfos != null);
+ Collection<MidiDeviceInfo> universalDeviceInfos = midiManager.getDevicesForTransport(
+ MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS);
+ assertTrue("Universal Device list was null.", universalDeviceInfos != null);
MidiManager.DeviceCallback callback = new MidiManager.DeviceCallback();
// These should not crash.
midiManager.unregisterDeviceCallback(callback);
midiManager.registerDeviceCallback(callback, null);
+ midiManager.registerDeviceCallbackForTransport(callback, null,
+ MidiManager.TRANSPORT_MIDI_BYTE_STREAM);
+ midiManager.registerDeviceCallbackForTransport(callback, null,
+ MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS);
midiManager.unregisterDeviceCallback(callback);
midiManager.registerDeviceCallback(callback, new Handler(Looper.getMainLooper()));
midiManager.registerDeviceCallback(callback, new Handler(Looper.getMainLooper()));
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/VolumeShaperTest.java b/tests/tests/media/audio/src/android/media/audio/cts/VolumeShaperTest.java
index 3c373de..1408275 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/VolumeShaperTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/VolumeShaperTest.java
@@ -16,6 +16,9 @@
package android.media.audio.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.testng.Assert.assertThrows;
import android.app.ActivityManager;
@@ -34,15 +37,19 @@
import android.platform.test.annotations.AppModeFull;
import android.util.Log;
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
-import com.android.compatibility.common.util.CtsAndroidTestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
-import java.lang.AutoCloseable;
import java.util.Arrays;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
/**
* VolumeShaperTest is automated using VolumeShaper.getVolume() to verify that a ramp
* or a duck is at the expected volume level. Listening to some tests is also possible,
@@ -53,7 +60,8 @@
* adb logcat | grep VolumeShaperTest
*/
@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class VolumeShaperTest extends CtsAndroidTestCase {
+@RunWith(JUnitParamsRunner.class)
+public class VolumeShaperTest {
private static final String TAG = "VolumeShaperTest";
// ramp or duck time (duration) used in tests.
@@ -150,6 +158,13 @@
.setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC)
.build();
+ private static final VolumeShaper.Configuration[] MONOTONIC_RAMPS = {
+ MONOTONIC_TEST,
+ CUBIC_RAMP,
+ SCURVE_RAMP,
+ SINE_RAMP,
+ };
+
private static final VolumeShaper.Operation[] ALL_STANDARD_OPERATIONS = {
VolumeShaper.Operation.PLAY,
VolumeShaper.Operation.REVERSE,
@@ -343,6 +358,7 @@
}
@SmallTest
+ @Test
public void testVolumeShaperConfigurationBuilder() throws Exception {
final String TEST_NAME = "testVolumeShaperConfigurationBuilder";
@@ -500,6 +516,7 @@
} // testVolumeShaperConfigurationBuilder
@SmallTest
+ @Test
public void testVolumeShaperConfigurationParcelable() throws Exception {
final String TEST_NAME = "testVolumeShaperConfigurationParcelable";
@@ -525,6 +542,7 @@
} // testVolumeShaperConfigurationParcelable
@SmallTest
+ @Test
public void testVolumeShaperOperationParcelable() throws Exception {
final String TEST_NAME = "testVolumeShaperOperationParcelable";
@@ -553,6 +571,7 @@
// to crash due to memory or performance issues. Typically around 16 app based
// shapers are allowed by the audio server.
@SmallTest
+ @Test
public void testMaximumShapers() {
final String TEST_NAME = "testMaximumShapers";
if (!hasAudioOutput()) {
@@ -582,60 +601,122 @@
}
} // testMaximumShapers
- @LargeTest
- public void testPlayerDuck() throws Exception {
- final String TEST_NAME = "testPlayerDuck";
+ @Test
+ public void testDuckAudioTrack() throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+ runTestDuckPlayer("testDuckAudioTrack", player);
+ }
+ }
+
+ @Test
+ public void testDuckMediaPlayerNonOffloaded() throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+ runTestDuckPlayer("testDuckMediaPlayerNonOffloaded", player);
+ }
+ }
+
+ @Test
+ public void testDuckMediaPlayerOffloaded() throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+ runTestDuckPlayer("testDuckMediaPlayerOffloaded", player);
+ }
+ }
+
+ private void runTestDuckPlayer(String testName, Player player) throws Exception {
if (!hasAudioOutput()) {
Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
return;
}
- for (int p = 0; p < PLAYER_TYPES; ++p) {
- try ( Player player = createPlayer(p);
- VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
- ) {
- final String testName = TEST_NAME + " " + player.name();
+ try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) {
+ Log.d(TAG, testName + " starting");
+ player.start();
+ Thread.sleep(WARMUP_TIME_MS);
- Log.d(TAG, testName + " starting");
- player.start();
- Thread.sleep(WARMUP_TIME_MS);
-
- runDuckTest(testName, volumeShaper);
- runCloseTest(testName, volumeShaper);
- }
+ runDuckTest(testName, volumeShaper);
+ runCloseTest(testName, volumeShaper);
}
- } // testPlayerDuck
+ }
- @LargeTest
- public void testPlayerRamp() throws Exception {
- final String TEST_NAME = "testPlayerRamp";
+ private VolumeShaper.Configuration[] getAllStandardRamps() {
+ return ALL_STANDARD_RAMPS;
+ }
+
+ // Parameters are indices to ALL_STANDARD_RAMPS configuration array.
+ @Test
+ @Parameters(method = "getAllStandardRamps")
+ public void testRampAudioTrack(
+ VolumeShaper.Configuration configuration) throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+ runTestRampPlayer("testRampAudioTrack", player, configuration);
+ }
+ }
+
+ // Parameters are indices to ALL_STANDARD_RAMPS configuration array.
+ @Test
+ @Parameters(method = "getAllStandardRamps")
+ public void testRampMediaPlayerNonOffloaded(
+ VolumeShaper.Configuration configuration) throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+ runTestRampPlayer("testRampMediaPlayerNonOffloaded", player, configuration);
+ }
+ }
+
+ // Parameters are indices to ALL_STANDARD_RAMPS configuration array.
+ @Test
+ @Parameters(method = "getAllStandardRamps")
+ public void testRampMediaPlayerOffloaded(
+ VolumeShaper.Configuration configuration) throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+ runTestRampPlayer("testRampMediaPlayerOffloaded", player, configuration);
+ }
+ }
+
+ private void runTestRampPlayer(
+ String testName, Player player, VolumeShaper.Configuration configuration)
+ throws Exception {
if (!hasAudioOutput()) {
Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
return;
}
- for (int p = 0; p < PLAYER_TYPES; ++p) {
- try ( Player player = createPlayer(p);
- VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
- ) {
- final String testName = TEST_NAME + " " + player.name();
+ try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) {
+ Log.d(TAG, testName + " starting");
+ player.start();
+ Thread.sleep(WARMUP_TIME_MS);
- Log.d(TAG, testName + " starting");
- player.start();
- Thread.sleep(WARMUP_TIME_MS);
-
- runRampTest(testName, volumeShaper);
- runCloseTest(testName, volumeShaper);
- }
+ runRampTest(testName, volumeShaper, configuration);
+ runCloseTest(testName, volumeShaper);
}
- } // testPlayerRamp
+ }
@FlakyTest
- @LargeTest
- public void testPlayerCornerCase() throws Exception {
- final String TEST_NAME = "testPlayerCornerCase";
+ @Test
+ public void testCornerCaseAudioTrack() throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+ runTestCornerCasePlayer("testCornerCaseAudioTrack", player);
+ }
+ }
+
+ @FlakyTest
+ @Test
+ public void testCornerCaseMediaPlayerNonOffloaded() throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+ runTestCornerCasePlayer("testCornerCaseMediaPlayerNonOffloaded", player);
+ }
+ }
+
+ @FlakyTest
+ @Test
+ public void testCornerCaseMediaPlayerOffloaded() throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+ runTestCornerCasePlayer("testCornerCaseMediaPlayerOffloaded", player);
+ }
+ }
+
+ private void runTestCornerCasePlayer(String testName, Player player) throws Exception {
if (!hasAudioOutput()) {
Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
@@ -643,48 +724,40 @@
}
final VolumeShaper.Configuration config = LINEAR_RAMP;
+ VolumeShaper volumeShaper = null;
+ try {
+ volumeShaper = player.createVolumeShaper(config);
- for (int p = 0; p < PLAYER_TYPES; ++p) {
- Player player = null;
- VolumeShaper volumeShaper = null;
- try {
- player = createPlayer(p);
- volumeShaper = player.createVolumeShaper(config);
- final String testName = TEST_NAME + " " + player.name();
+ runStartIdleTest(testName, volumeShaper, player);
+ runRampCornerCaseTest(testName, volumeShaper, config);
+ runCloseTest(testName, volumeShaper);
- runStartIdleTest(testName, volumeShaper, player);
- runRampCornerCaseTest(testName, volumeShaper, config);
- runCloseTest(testName, volumeShaper);
+ Log.d(TAG, testName + " recreating VolumeShaper and repeating with pause");
+ volumeShaper = player.createVolumeShaper(config);
+ player.pause();
+ Thread.sleep(100 /* millis */);
+ runStartIdleTest(testName, volumeShaper, player);
- Log.d(TAG, testName + " recreating VolumeShaper and repeating with pause");
- volumeShaper = player.createVolumeShaper(config);
- player.pause();
- Thread.sleep(100 /* millis */);
- runStartIdleTest(testName, volumeShaper, player);
+ // volumeShaper not explicitly closed, will close upon finalize or player close.
+ Log.d(TAG, testName + " recreating VolumeShaper and repeating with stop");
+ volumeShaper = player.createVolumeShaper(config);
+ player.stop();
+ Thread.sleep(100 /* millis */);
+ runStartIdleTest(testName, volumeShaper, player);
- // volumeShaper not explicitly closed, will close upon finalize or player close.
- Log.d(TAG, testName + " recreating VolumeShaper and repeating with stop");
- volumeShaper = player.createVolumeShaper(config);
- player.stop();
- Thread.sleep(100 /* millis */);
- runStartIdleTest(testName, volumeShaper, player);
-
- Log.d(TAG, testName + " closing Player before VolumeShaper");
- player.close();
- runCloseTest2(testName, volumeShaper);
- } finally {
- if (volumeShaper != null) {
- volumeShaper.close();
- }
- if (player != null) {
- player.close();
- }
+ Log.d(TAG, testName + " closing Player before VolumeShaper");
+ player.close();
+ runCloseTest2(testName, volumeShaper);
+ } finally {
+ if (volumeShaper != null) {
+ volumeShaper.close();
}
}
- } // testPlayerCornerCase
+ } // runTestCornerCasePlayer
@FlakyTest
@LargeTest
+ @Test
public void testPlayerCornerCase2() throws Exception {
final String TEST_NAME = "testPlayerCornerCase2";
if (!hasAudioOutput()) {
@@ -728,6 +801,7 @@
@FlakyTest
@LargeTest
+ @Test
public void testPlayerJoin() throws Exception {
final String TEST_NAME = "testPlayerJoin";
if (!hasAudioOutput()) {
@@ -747,7 +821,7 @@
Log.d(TAG, " we join several LINEAR_RAMPS together "
+ " this effectively is one LINEAR_RAMP (volume increasing).");
- final long durationMs = 10000;
+ final long durationMs = 5000;
final long incrementMs = 1000;
for (long i = 0; i < durationMs; i += incrementMs) {
Log.d(TAG, testName + " Play - join " + i);
@@ -764,133 +838,174 @@
}
} // testPlayerJoin
- @LargeTest
- public void testPlayerCubicMonotonic() throws Exception {
- final String TEST_NAME = "testPlayerCubicMonotonic";
+ private VolumeShaper.Configuration[] getMonotonicRamps() {
+ return MONOTONIC_RAMPS;
+ }
+
+ // Parameters are indices to MONOTONIC_RAMPS configuration array.
+ @Test
+ @Parameters(method = "getMonotonicRamps")
+ public void testCubicMonotonicAudioTrack(
+ VolumeShaper.Configuration configuration) throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+ runTestCubicMonotonicPlayer(
+ "testCubicMonotonicAudioTrack", player, configuration);
+ }
+ }
+
+ // Parameters are indices to MONOTONIC_RAMPS configuration array.
+ @Test
+ @Parameters(method = "getMonotonicRamps")
+ public void testCubicMonotonicMediaPlayerNonOffloaded(
+ VolumeShaper.Configuration configuration) throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+ runTestCubicMonotonicPlayer(
+ "testCubicMonotonicMediaPlayerNonOffloaded", player, configuration);
+ }
+ }
+
+ // Parameters are indices to MONOTONIC_RAMPS configuration array.
+ @Test
+ @Parameters(method = "getMonotonicRamps")
+ public void testCubicMonotonicMediaPlayerOffloaded(
+ VolumeShaper.Configuration configuration) throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+ runTestCubicMonotonicPlayer(
+ "testCubicMonotonicMediaPlayerOffloaded", player, configuration);
+ }
+ }
+
+ private void runTestCubicMonotonicPlayer(
+ String testName, Player player, VolumeShaper.Configuration configuration)
+ throws Exception {
if (!hasAudioOutput()) {
Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
return;
}
- final VolumeShaper.Configuration configurations[] =
- new VolumeShaper.Configuration[] {
- MONOTONIC_TEST,
- CUBIC_RAMP,
- SCURVE_RAMP,
- SINE_RAMP,
- };
+ try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) {
+ volumeShaper.apply(VolumeShaper.Operation.PLAY);
+ player.start();
+ Thread.sleep(WARMUP_TIME_MS);
- for (int p = 0; p < PLAYER_TYPES; ++p) {
- try ( Player player = createPlayer(p);
- VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
- ) {
- final String testName = TEST_NAME + " " + player.name();
- volumeShaper.apply(VolumeShaper.Operation.PLAY);
- player.start();
- Thread.sleep(WARMUP_TIME_MS);
+ // test configurations known monotonic
+ Log.d(TAG, testName + " starting test");
- for (VolumeShaper.Configuration configuration : configurations) {
- // test configurations known monotonic
- Log.d(TAG, testName + " starting test");
+ float lastVolume = 0;
+ final long incrementMs = 100;
- float lastVolume = 0;
- final long incrementMs = 100;
-
- volumeShaper.replace(configuration,
- VolumeShaper.Operation.PLAY, true /* join */);
- // monotonicity test
- for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
- final float volume = volumeShaper.getVolume();
- assertTrue(testName + " montonic volume should increase "
- + volume + " >= " + lastVolume,
- (volume >= lastVolume));
- lastVolume = volume;
- Thread.sleep(incrementMs);
- }
- Thread.sleep(WARMUP_TIME_MS);
- lastVolume = volumeShaper.getVolume();
- assertEquals(testName
- + " final monotonic value should be 1.f, but is " + lastVolume,
- 1.f, lastVolume, VOLUME_TOLERANCE);
-
- Log.d(TAG, "invert");
- // invert
- VolumeShaper.Configuration newConfiguration =
- new VolumeShaper.Configuration.Builder(configuration)
- .invertVolumes()
- .build();
- volumeShaper.replace(newConfiguration,
- VolumeShaper.Operation.PLAY, true /* join */);
- // monotonicity test
- for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
- final float volume = volumeShaper.getVolume();
- assertTrue(testName + " montonic volume should decrease "
- + volume + " <= " + lastVolume,
- (volume <= lastVolume));
- lastVolume = volume;
- Thread.sleep(incrementMs);
- }
- Thread.sleep(WARMUP_TIME_MS);
- lastVolume = volumeShaper.getVolume();
- assertEquals(testName
- + " final monotonic value should be 0.f, but is " + lastVolume,
- 0.f, lastVolume, VOLUME_TOLERANCE);
-
- // invert + reflect
- Log.d(TAG, "invert and reflect");
- newConfiguration =
- new VolumeShaper.Configuration.Builder(configuration)
- .invertVolumes()
- .reflectTimes()
- .build();
- volumeShaper.replace(newConfiguration,
- VolumeShaper.Operation.PLAY, true /* join */);
- // monotonicity test
- for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
- final float volume = volumeShaper.getVolume();
- assertTrue(testName + " montonic volume should increase "
- + volume + " >= " + lastVolume,
- (volume >= lastVolume - VOLUME_TOLERANCE));
- lastVolume = volume;
- Thread.sleep(incrementMs);
- }
- Thread.sleep(WARMUP_TIME_MS);
- lastVolume = volumeShaper.getVolume();
- assertEquals(testName
- + " final monotonic value should be 1.f, but is " + lastVolume,
- 1.f, lastVolume, VOLUME_TOLERANCE);
-
- // reflect
- Log.d(TAG, "reflect");
- newConfiguration =
- new VolumeShaper.Configuration.Builder(configuration)
- .reflectTimes()
- .build();
- volumeShaper.replace(newConfiguration,
- VolumeShaper.Operation.PLAY, true /* join */);
- // monotonicity test
- for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
- final float volume = volumeShaper.getVolume();
- assertTrue(testName + " montonic volume should decrease "
- + volume + " <= " + lastVolume,
- (volume <= lastVolume));
- lastVolume = volume;
- Thread.sleep(incrementMs);
- }
- Thread.sleep(WARMUP_TIME_MS);
- lastVolume = volumeShaper.getVolume();
- assertEquals(testName
- + " final monotonic value should be 0.f, but is " + lastVolume,
- 0.f, lastVolume, VOLUME_TOLERANCE);
- }
+ volumeShaper.replace(configuration,
+ VolumeShaper.Operation.PLAY, true /* join */);
+ // monotonicity test
+ for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
+ final float volume = volumeShaper.getVolume();
+ assertTrue(testName + " montonic volume should increase "
+ + volume + " >= " + lastVolume,
+ (volume >= lastVolume));
+ lastVolume = volume;
+ Thread.sleep(incrementMs);
}
- }
- } // testPlayerCubicMonotonic
+ Thread.sleep(WARMUP_TIME_MS);
+ lastVolume = volumeShaper.getVolume();
+ assertEquals(testName
+ + " final monotonic value should be 1.f, but is " + lastVolume,
+ 1.f, lastVolume, VOLUME_TOLERANCE);
- @LargeTest
- public void testPlayerStepRamp() throws Exception {
- final String TEST_NAME = "testPlayerStepRamp";
+ Log.d(TAG, "invert");
+ // invert
+ VolumeShaper.Configuration newConfiguration =
+ new VolumeShaper.Configuration.Builder(configuration)
+ .invertVolumes()
+ .build();
+ volumeShaper.replace(newConfiguration,
+ VolumeShaper.Operation.PLAY, true /* join */);
+ // monotonicity test
+ for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
+ final float volume = volumeShaper.getVolume();
+ assertTrue(testName + " montonic volume should decrease "
+ + volume + " <= " + lastVolume,
+ (volume <= lastVolume));
+ lastVolume = volume;
+ Thread.sleep(incrementMs);
+ }
+ Thread.sleep(WARMUP_TIME_MS);
+ lastVolume = volumeShaper.getVolume();
+ assertEquals(testName
+ + " final monotonic value should be 0.f, but is " + lastVolume,
+ 0.f, lastVolume, VOLUME_TOLERANCE);
+
+ // invert + reflect
+ Log.d(TAG, "invert and reflect");
+ newConfiguration =
+ new VolumeShaper.Configuration.Builder(configuration)
+ .invertVolumes()
+ .reflectTimes()
+ .build();
+ volumeShaper.replace(newConfiguration,
+ VolumeShaper.Operation.PLAY, true /* join */);
+ // monotonicity test
+ for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
+ final float volume = volumeShaper.getVolume();
+ assertTrue(testName + " montonic volume should increase "
+ + volume + " >= " + lastVolume,
+ (volume >= lastVolume - VOLUME_TOLERANCE));
+ lastVolume = volume;
+ Thread.sleep(incrementMs);
+ }
+ Thread.sleep(WARMUP_TIME_MS);
+ lastVolume = volumeShaper.getVolume();
+ assertEquals(testName
+ + " final monotonic value should be 1.f, but is " + lastVolume,
+ 1.f, lastVolume, VOLUME_TOLERANCE);
+
+ // reflect
+ Log.d(TAG, "reflect");
+ newConfiguration =
+ new VolumeShaper.Configuration.Builder(configuration)
+ .reflectTimes()
+ .build();
+ volumeShaper.replace(newConfiguration,
+ VolumeShaper.Operation.PLAY, true /* join */);
+ // monotonicity test
+ for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
+ final float volume = volumeShaper.getVolume();
+ assertTrue(testName + " montonic volume should decrease "
+ + volume + " <= " + lastVolume,
+ (volume <= lastVolume));
+ lastVolume = volume;
+ Thread.sleep(incrementMs);
+ }
+ Thread.sleep(WARMUP_TIME_MS);
+ lastVolume = volumeShaper.getVolume();
+ assertEquals(testName
+ + " final monotonic value should be 0.f, but is " + lastVolume,
+ 0.f, lastVolume, VOLUME_TOLERANCE);
+ }
+ } // runTestCubicMonotonicPlayer
+
+ @Test
+ public void testStepRampAudioTrack() throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+ runTestStepRampPlayer("testStepRampAudioTrack", player);
+ }
+ }
+
+ @Test
+ public void testStepRampMediaPlayerNonOffloaded() throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+ runTestStepRampPlayer("testStepRampMediaPlayerNonOffloaded", player);
+ }
+ }
+
+ @Test
+ public void testStepRampMediaPlayerOffloaded() throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+ runTestStepRampPlayer("testStepRampMediaPlayerOffloaded", player);
+ }
+ }
+
+ private void runTestStepRampPlayer(String testName, Player player) throws Exception {
if (!hasAudioOutput()) {
Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
@@ -902,121 +1017,137 @@
// It should suddenly jump to full volume at 1.f (full duration).
// Note: invertVolumes() and reflectTimes() are not symmetric for STEP interpolation;
// however, VolumeShaper.Operation.REVERSE will behave symmetrically.
- for (int p = 0; p < PLAYER_TYPES; ++p) {
- try ( Player player = createPlayer(p);
- VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
- ) {
- final String testName = TEST_NAME + " " + player.name();
- volumeShaper.apply(VolumeShaper.Operation.PLAY);
- player.start();
- Thread.sleep(WARMUP_TIME_MS);
- final VolumeShaper.Configuration configuration = STEP_RAMP;
- Log.d(TAG, testName + " starting test (sudden jump to full after "
- + RAMP_TIME_MS + " milliseconds)");
+ try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) {
+ volumeShaper.apply(VolumeShaper.Operation.PLAY);
+ player.start();
+ Thread.sleep(WARMUP_TIME_MS);
- volumeShaper.replace(configuration,
- VolumeShaper.Operation.PLAY, true /* join */);
+ final VolumeShaper.Configuration configuration = STEP_RAMP;
+ Log.d(TAG, testName + " starting test (sudden jump to full after "
+ + RAMP_TIME_MS + " milliseconds)");
- Thread.sleep(RAMP_TIME_MS / 2);
- float lastVolume = volumeShaper.getVolume();
- assertEquals(testName
- + " middle value should be 0.f, but is " + lastVolume,
- 0.f, lastVolume, VOLUME_TOLERANCE);
+ volumeShaper.replace(configuration,
+ VolumeShaper.Operation.PLAY, true /* join */);
- Thread.sleep(RAMP_TIME_MS / 2 + 1000);
- lastVolume = volumeShaper.getVolume();
- assertEquals(testName
- + " final value should be 1.f, but is " + lastVolume,
- 1.f, lastVolume, VOLUME_TOLERANCE);
+ Thread.sleep(RAMP_TIME_MS / 2);
+ float lastVolume = volumeShaper.getVolume();
+ assertEquals(testName
+ + " middle value should be 0.f, but is " + lastVolume,
+ 0.f, lastVolume, VOLUME_TOLERANCE);
- Log.d(TAG, "invert (sudden jump to silence after "
- + RAMP_TIME_MS + " milliseconds)");
- // invert
- VolumeShaper.Configuration newConfiguration =
- new VolumeShaper.Configuration.Builder(configuration)
- .invertVolumes()
- .build();
- volumeShaper.replace(newConfiguration,
- VolumeShaper.Operation.PLAY, true /* join */);
+ Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+ lastVolume = volumeShaper.getVolume();
+ assertEquals(testName
+ + " final value should be 1.f, but is " + lastVolume,
+ 1.f, lastVolume, VOLUME_TOLERANCE);
- Thread.sleep(RAMP_TIME_MS / 2);
- lastVolume = volumeShaper.getVolume();
- assertEquals(testName
- + " middle value should be 1.f, but is " + lastVolume,
- 1.f, lastVolume, VOLUME_TOLERANCE);
+ Log.d(TAG, "invert (sudden jump to silence after "
+ + RAMP_TIME_MS + " milliseconds)");
+ // invert
+ VolumeShaper.Configuration newConfiguration =
+ new VolumeShaper.Configuration.Builder(configuration)
+ .invertVolumes()
+ .build();
+ volumeShaper.replace(newConfiguration,
+ VolumeShaper.Operation.PLAY, true /* join */);
- Thread.sleep(RAMP_TIME_MS / 2 + 1000);
- lastVolume = volumeShaper.getVolume();
- assertEquals(testName
- + " final value should be 0.f, but is " + lastVolume,
- 0.f, lastVolume, VOLUME_TOLERANCE);
+ Thread.sleep(RAMP_TIME_MS / 2);
+ lastVolume = volumeShaper.getVolume();
+ assertEquals(testName
+ + " middle value should be 1.f, but is " + lastVolume,
+ 1.f, lastVolume, VOLUME_TOLERANCE);
- // invert + reflect
- Log.d(TAG, "invert and reflect (sudden jump to full after "
- + RAMP_TIME_MS + " milliseconds)");
- newConfiguration =
- new VolumeShaper.Configuration.Builder(configuration)
- .invertVolumes()
- .reflectTimes()
- .build();
- volumeShaper.replace(newConfiguration,
- VolumeShaper.Operation.PLAY, true /* join */);
+ Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+ lastVolume = volumeShaper.getVolume();
+ assertEquals(testName
+ + " final value should be 0.f, but is " + lastVolume,
+ 0.f, lastVolume, VOLUME_TOLERANCE);
- Thread.sleep(RAMP_TIME_MS / 2);
- lastVolume = volumeShaper.getVolume();
- assertEquals(testName
- + " middle value should be 0.f, but is " + lastVolume,
- 0.f, lastVolume, VOLUME_TOLERANCE);
+ // invert + reflect
+ Log.d(TAG, "invert and reflect (sudden jump to full after "
+ + RAMP_TIME_MS + " milliseconds)");
+ newConfiguration =
+ new VolumeShaper.Configuration.Builder(configuration)
+ .invertVolumes()
+ .reflectTimes()
+ .build();
+ volumeShaper.replace(newConfiguration,
+ VolumeShaper.Operation.PLAY, true /* join */);
- Thread.sleep(RAMP_TIME_MS / 2 + 1000);
- lastVolume = volumeShaper.getVolume();
- assertEquals(testName
- + " final value should be 1.f, but is " + lastVolume,
- 1.f, lastVolume, VOLUME_TOLERANCE);
+ Thread.sleep(RAMP_TIME_MS / 2);
+ lastVolume = volumeShaper.getVolume();
+ assertEquals(testName
+ + " middle value should be 0.f, but is " + lastVolume,
+ 0.f, lastVolume, VOLUME_TOLERANCE);
- // reflect
- Log.d(TAG, "reflect (sudden jump to silence after "
- + RAMP_TIME_MS + " milliseconds)");
- newConfiguration =
- new VolumeShaper.Configuration.Builder(configuration)
- .reflectTimes()
- .build();
- volumeShaper.replace(newConfiguration,
- VolumeShaper.Operation.PLAY, true /* join */);
+ Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+ lastVolume = volumeShaper.getVolume();
+ assertEquals(testName
+ + " final value should be 1.f, but is " + lastVolume,
+ 1.f, lastVolume, VOLUME_TOLERANCE);
- Thread.sleep(RAMP_TIME_MS / 2);
- lastVolume = volumeShaper.getVolume();
- assertEquals(testName
- + " middle value should be 1.f, but is " + lastVolume,
- 1.f, lastVolume, VOLUME_TOLERANCE);
+ // reflect
+ Log.d(TAG, "reflect (sudden jump to silence after "
+ + RAMP_TIME_MS + " milliseconds)");
+ newConfiguration =
+ new VolumeShaper.Configuration.Builder(configuration)
+ .reflectTimes()
+ .build();
+ volumeShaper.replace(newConfiguration,
+ VolumeShaper.Operation.PLAY, true /* join */);
- Thread.sleep(RAMP_TIME_MS / 2 + 1000);
- lastVolume = volumeShaper.getVolume();
- assertEquals(testName
- + " final value should be 0.f, but is " + lastVolume,
- 0.f, lastVolume, VOLUME_TOLERANCE);
+ Thread.sleep(RAMP_TIME_MS / 2);
+ lastVolume = volumeShaper.getVolume();
+ assertEquals(testName
+ + " middle value should be 1.f, but is " + lastVolume,
+ 1.f, lastVolume, VOLUME_TOLERANCE);
- Log.d(TAG, "reverse (immediate jump to full)");
- volumeShaper.apply(VolumeShaper.Operation.REVERSE);
- Thread.sleep(RAMP_TIME_MS / 2);
- lastVolume = volumeShaper.getVolume();
- assertEquals(testName
- + " middle value should be 1.f, but is " + lastVolume,
- 1.f, lastVolume, VOLUME_TOLERANCE);
+ Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+ lastVolume = volumeShaper.getVolume();
+ assertEquals(testName
+ + " final value should be 0.f, but is " + lastVolume,
+ 0.f, lastVolume, VOLUME_TOLERANCE);
- Thread.sleep(RAMP_TIME_MS / 2 + 1000);
- lastVolume = volumeShaper.getVolume();
- assertEquals(testName
- + " final value should be 1.f, but is " + lastVolume,
- 1.f, lastVolume, VOLUME_TOLERANCE);
- }
+ Log.d(TAG, "reverse (immediate jump to full)");
+ volumeShaper.apply(VolumeShaper.Operation.REVERSE);
+ Thread.sleep(RAMP_TIME_MS / 2);
+ lastVolume = volumeShaper.getVolume();
+ assertEquals(testName
+ + " middle value should be 1.f, but is " + lastVolume,
+ 1.f, lastVolume, VOLUME_TOLERANCE);
+
+ Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+ lastVolume = volumeShaper.getVolume();
+ assertEquals(testName
+ + " final value should be 1.f, but is " + lastVolume,
+ 1.f, lastVolume, VOLUME_TOLERANCE);
}
- } // testPlayerStepRamp
+ } // runTestStepRampPlayer
- @LargeTest
- public void testPlayerTwoShapers() throws Exception {
- final String TEST_NAME = "testPlayerTwoShapers";
+ @Test
+ public void testTwoShapersAudioTrack() throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+ runTestTwoShapersPlayer("testTwoShapersAudioTrack", player);
+ }
+ }
+
+ @Test
+ public void testTwoShapersMediaPlayerNonOffloaded() throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+ runTestTwoShapersPlayer("testTwoShapersMediaPlayerNonOffloaded", player);
+ }
+ }
+
+ @Test
+ public void testTwoShapersMediaPlayerOffloaded() throws Exception {
+ try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+ runTestTwoShapersPlayer("testTwoShapersMediaPlayerOffloaded", player);
+ }
+ }
+
+ private void runTestTwoShapersPlayer(String testName, Player player) throws Exception {
+
if (!hasAudioOutput()) {
Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
@@ -1036,72 +1167,69 @@
.reflectTimes()
.build();
- for (int p = 0; p < PLAYER_TYPES; ++p) {
- try ( Player player = createPlayer(p);
- VolumeShaper volumeShaperRamp = player.createVolumeShaper(LONG_RAMP);
- VolumeShaper volumeShaperDuck = player.createVolumeShaper(LONG_DUCK);
- ) {
- final String testName = TEST_NAME + " " + player.name();
+ try (
+ VolumeShaper volumeShaperRamp = player.createVolumeShaper(LONG_RAMP);
+ VolumeShaper volumeShaperDuck = player.createVolumeShaper(LONG_DUCK);
+ ) {
+ final float firstVolumeRamp = volumeShaperRamp.getVolume();
+ final float firstVolumeDuck = volumeShaperDuck.getVolume();
+ assertEquals(testName
+ + " first ramp value should be 0.f, but is " + firstVolumeRamp,
+ 0.f, firstVolumeRamp, VOLUME_TOLERANCE);
+ assertEquals(testName
+ + " first duck value should be 1.f, but is " + firstVolumeDuck,
+ 1.f, firstVolumeDuck, VOLUME_TOLERANCE);
+ player.start();
- final float firstVolumeRamp = volumeShaperRamp.getVolume();
- final float firstVolumeDuck = volumeShaperDuck.getVolume();
- assertEquals(testName
- + " first ramp value should be 0.f, but is " + firstVolumeRamp,
- 0.f, firstVolumeRamp, VOLUME_TOLERANCE);
- assertEquals(testName
- + " first duck value should be 1.f, but is " + firstVolumeDuck,
- 1.f, firstVolumeDuck, VOLUME_TOLERANCE);
- player.start();
+ Thread.sleep(1000);
- Thread.sleep(1000);
+ final float lastVolumeRamp = volumeShaperRamp.getVolume();
+ final float lastVolumeDuck = volumeShaperDuck.getVolume();
+ assertEquals(testName
+ + " no-play ramp value should be 0.f, but is " + lastVolumeRamp,
+ 0.f, lastVolumeRamp, VOLUME_TOLERANCE);
+ assertEquals(testName
+ + " no-play duck value should be 1.f, but is " + lastVolumeDuck,
+ 1.f, lastVolumeDuck, VOLUME_TOLERANCE);
- final float lastVolumeRamp = volumeShaperRamp.getVolume();
- final float lastVolumeDuck = volumeShaperDuck.getVolume();
- assertEquals(testName
- + " no-play ramp value should be 0.f, but is " + lastVolumeRamp,
- 0.f, lastVolumeRamp, VOLUME_TOLERANCE);
- assertEquals(testName
- + " no-play duck value should be 1.f, but is " + lastVolumeDuck,
- 1.f, lastVolumeDuck, VOLUME_TOLERANCE);
+ Log.d(TAG, testName + " volume should be silent and start increasing now");
- Log.d(TAG, testName + " volume should be silent and start increasing now");
+ // we actually start now!
+ volumeShaperRamp.apply(VolumeShaper.Operation.PLAY);
+ volumeShaperDuck.apply(VolumeShaper.Operation.PLAY);
+ Thread.sleep(durationMs / 2);
- // we actually start now!
- volumeShaperRamp.apply(VolumeShaper.Operation.PLAY);
- volumeShaperDuck.apply(VolumeShaper.Operation.PLAY);
- Thread.sleep(durationMs / 2);
+ Log.d(TAG, testName + " volume should be > 0 and about maximum here");
+ final float lastVolumeRamp2 = volumeShaperRamp.getVolume();
+ final float lastVolumeDuck2 = volumeShaperDuck.getVolume();
+ assertTrue(testName
+ + " last ramp value should be > 0.f " + lastVolumeRamp2,
+ lastVolumeRamp2 > 0.f);
+ assertTrue(testName
+ + " last duck value should be < 1.f " + lastVolumeDuck2,
+ lastVolumeDuck2 < 1.f);
- Log.d(TAG, testName + " volume should be > 0 and about maximum here");
- final float lastVolumeRamp2 = volumeShaperRamp.getVolume();
- final float lastVolumeDuck2 = volumeShaperDuck.getVolume();
- assertTrue(testName
- + " last ramp value should be > 0.f " + lastVolumeRamp2,
- lastVolumeRamp2 > 0.f);
- assertTrue(testName
- + " last duck value should be < 1.f " + lastVolumeDuck2,
- lastVolumeDuck2 < 1.f);
+ Log.d(TAG, testName + " volume should start decreasing shortly");
+ Thread.sleep(durationMs / 2 + 1000);
- Log.d(TAG, testName + " volume should start decreasing shortly");
- Thread.sleep(durationMs / 2 + 1000);
+ Log.d(TAG, testName + " volume should be silent now");
+ final float lastVolumeRamp3 = volumeShaperRamp.getVolume();
+ final float lastVolumeDuck3 = volumeShaperDuck.getVolume();
+ assertEquals(testName
+ + " last ramp value should be 1.f, but is " + lastVolumeRamp3,
+ 1.f, lastVolumeRamp3, VOLUME_TOLERANCE);
+ assertEquals(testName
+ + " last duck value should be 0.f, but is " + lastVolumeDuck3,
+ 0.f, lastVolumeDuck3, VOLUME_TOLERANCE);
- Log.d(TAG, testName + " volume should be silent now");
- final float lastVolumeRamp3 = volumeShaperRamp.getVolume();
- final float lastVolumeDuck3 = volumeShaperDuck.getVolume();
- assertEquals(testName
- + " last ramp value should be 1.f, but is " + lastVolumeRamp3,
- 1.f, lastVolumeRamp3, VOLUME_TOLERANCE);
- assertEquals(testName
- + " last duck value should be 0.f, but is " + lastVolumeDuck3,
- 0.f, lastVolumeDuck3, VOLUME_TOLERANCE);
-
- runCloseTest(testName, volumeShaperRamp);
- runCloseTest(testName, volumeShaperDuck);
- }
+ runCloseTest(testName, volumeShaperRamp);
+ runCloseTest(testName, volumeShaperDuck);
}
- } // testPlayerTwoShapers
+ } // runTestTwoShapersPlayer
// tests that shaper advances in the presence of pause and stop (time based after start).
@LargeTest
+ @Test
public void testPlayerRunDuringPauseStop() throws Exception {
final String TEST_NAME = "testPlayerRunDuringPauseStop";
if (!hasAudioOutput()) {
@@ -1156,55 +1284,55 @@
} // testPlayerRunDuringPauseStop
// Player should be started before calling (as it is not an argument to method).
- private void runRampTest(String testName, VolumeShaper volumeShaper) throws Exception {
- for (VolumeShaper.Configuration config : ALL_STANDARD_RAMPS) {
- // This replaces with play.
- Log.d(TAG, testName + " Replace + Play (volume should increase)");
- volumeShaper.replace(config, VolumeShaper.Operation.PLAY, false /* join */);
- Thread.sleep(RAMP_TIME_MS / 2);
+ private void runRampTest(
+ String testName, VolumeShaper volumeShaper, VolumeShaper.Configuration config)
+ throws Exception {
+ // This replaces with play.
+ Log.d(TAG, testName + " Replace + Play (volume should increase)");
+ volumeShaper.replace(config, VolumeShaper.Operation.PLAY, false /* join */);
+ Thread.sleep(RAMP_TIME_MS / 2);
- // Reverse the direction of the volume shaper curve
- Log.d(TAG, testName + " Reverse (volume should decrease)");
- volumeShaper.apply(VolumeShaper.Operation.REVERSE);
- Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+ // Reverse the direction of the volume shaper curve
+ Log.d(TAG, testName + " Reverse (volume should decrease)");
+ volumeShaper.apply(VolumeShaper.Operation.REVERSE);
+ Thread.sleep(RAMP_TIME_MS / 2 + 1000);
- Log.d(TAG, testName + " Check Volume (silent)");
- assertEquals(testName + " volume should be 0.f",
- 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+ Log.d(TAG, testName + " Check Volume (silent)");
+ assertEquals(testName + " volume should be 0.f",
+ 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
- // Forwards
- Log.d(TAG, testName + " Play (volume should increase)");
- volumeShaper.apply(VolumeShaper.Operation.PLAY);
- Thread.sleep(RAMP_TIME_MS + 1000);
+ // Forwards
+ Log.d(TAG, testName + " Play (volume should increase)");
+ volumeShaper.apply(VolumeShaper.Operation.PLAY);
+ Thread.sleep(RAMP_TIME_MS + 1000);
- Log.d(TAG, testName + " Check Volume (volume at max)");
- assertEquals(testName + " volume should be 1.f",
- 1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+ Log.d(TAG, testName + " Check Volume (volume at max)");
+ assertEquals(testName + " volume should be 1.f",
+ 1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
- // Reverse
- Log.d(TAG, testName + " Reverse (volume should decrease)");
- volumeShaper.apply(VolumeShaper.Operation.REVERSE);
- Thread.sleep(RAMP_TIME_MS + 1000);
+ // Reverse
+ Log.d(TAG, testName + " Reverse (volume should decrease)");
+ volumeShaper.apply(VolumeShaper.Operation.REVERSE);
+ Thread.sleep(RAMP_TIME_MS + 1000);
- Log.d(TAG, testName + " Check Volume (volume should be silent)");
- assertEquals(testName + " volume should be 0.f",
- 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+ Log.d(TAG, testName + " Check Volume (volume should be silent)");
+ assertEquals(testName + " volume should be 0.f",
+ 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
- // Forwards
- Log.d(TAG, testName + " Play (volume should increase)");
- volumeShaper.apply(VolumeShaper.Operation.PLAY);
- Thread.sleep(RAMP_TIME_MS + 1000);
+ // Forwards
+ Log.d(TAG, testName + " Play (volume should increase)");
+ volumeShaper.apply(VolumeShaper.Operation.PLAY);
+ Thread.sleep(RAMP_TIME_MS + 1000);
- // Comment out for headset plug/unplug test
- // Log.d(TAG, testName + " headset check"); Thread.sleep(10000 /* millis */);
- //
+ // Comment out for headset plug/unplug test
+ // Log.d(TAG, testName + " headset check"); Thread.sleep(10000 /* millis */);
+ //
- Log.d(TAG, testName + " Check Volume (volume at max)");
- assertEquals(testName + " volume should be 1.f",
- 1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+ Log.d(TAG, testName + " Check Volume (volume at max)");
+ assertEquals(testName + " volume should be 1.f",
+ 1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
- Log.d(TAG, testName + " done");
- }
+ Log.d(TAG, testName + " done");
} // runRampTest
// Player should be started before calling (as it is not an argument to method).
@@ -1392,4 +1520,8 @@
assertTrue(testName + " volume should be greater than 0.f",
volumeShaper.getVolume() > 0.f);
} // runStartSyncTest
+
+ private static Context getContext() {
+ return InstrumentationRegistry.getInstrumentation().getTargetContext();
+ }
}
diff --git a/tests/tests/media/common/src/android/media/cts/AudioHelper.java b/tests/tests/media/common/src/android/media/cts/AudioHelper.java
index 995acda..a5f61fe 100644
--- a/tests/tests/media/common/src/android/media/cts/AudioHelper.java
+++ b/tests/tests/media/common/src/android/media/cts/AudioHelper.java
@@ -533,7 +533,7 @@
.setBufferSizeInBytes(bufferOutSize)
.build();
Assert.assertEquals(AudioTrack.STATE_INITIALIZED, mTrack.getState());
- mPosition = 0;
+ mTrackPosition = 0;
mFinishAtMs = 0;
}
}
@@ -545,8 +545,9 @@
AudioFormat.ENCODING_PCM_8BIT, getAudioFormat());
int samples = super.read(audioData, offsetInBytes, sizeInBytes);
if (mTrack != null) {
- Assert.assertEquals(samples, mTrack.write(audioData, offsetInBytes, samples));
- mPosition += samples / mTrack.getChannelCount();
+ final int result = mTrack.write(audioData, offsetInBytes, samples,
+ AudioTrack.WRITE_NON_BLOCKING);
+ mTrackPosition += result / mTrack.getChannelCount();
}
return samples;
}
@@ -558,9 +559,9 @@
AudioFormat.ENCODING_PCM_8BIT, getAudioFormat());
int samples = super.read(audioData, offsetInBytes, sizeInBytes, readMode);
if (mTrack != null) {
- Assert.assertEquals(samples, mTrack.write(audioData, offsetInBytes, samples,
- AudioTrack.WRITE_BLOCKING));
- mPosition += samples / mTrack.getChannelCount();
+ final int result = mTrack.write(audioData, offsetInBytes, samples,
+ AudioTrack.WRITE_NON_BLOCKING);
+ mTrackPosition += result / mTrack.getChannelCount();
}
return samples;
}
@@ -572,8 +573,9 @@
AudioFormat.ENCODING_PCM_16BIT, getAudioFormat());
int samples = super.read(audioData, offsetInShorts, sizeInShorts);
if (mTrack != null) {
- Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples));
- mPosition += samples / mTrack.getChannelCount();
+ final int result = mTrack.write(audioData, offsetInShorts, samples,
+ AudioTrack.WRITE_NON_BLOCKING);
+ mTrackPosition += result / mTrack.getChannelCount();
}
return samples;
}
@@ -585,9 +587,9 @@
AudioFormat.ENCODING_PCM_16BIT, getAudioFormat());
int samples = super.read(audioData, offsetInShorts, sizeInShorts, readMode);
if (mTrack != null) {
- Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples,
- AudioTrack.WRITE_BLOCKING));
- mPosition += samples / mTrack.getChannelCount();
+ final int result = mTrack.write(audioData, offsetInShorts, samples,
+ AudioTrack.WRITE_NON_BLOCKING);
+ mTrackPosition += result / mTrack.getChannelCount();
}
return samples;
}
@@ -599,9 +601,9 @@
AudioFormat.ENCODING_PCM_FLOAT, getAudioFormat());
int samples = super.read(audioData, offsetInFloats, sizeInFloats, readMode);
if (mTrack != null) {
- Assert.assertEquals(samples, mTrack.write(audioData, offsetInFloats, samples,
- AudioTrack.WRITE_BLOCKING));
- mPosition += samples / mTrack.getChannelCount();
+ final int result = mTrack.write(audioData, offsetInFloats, samples,
+ AudioTrack.WRITE_NON_BLOCKING);
+ mTrackPosition += result / mTrack.getChannelCount();
}
return samples;
}
@@ -615,10 +617,9 @@
// which does check position and limit.
ByteBuffer copy = audioBuffer.duplicate();
copy.position(0).limit(bytes); // read places data at the start of the buffer.
- Assert.assertEquals(bytes, mTrack.write(copy, bytes, AudioTrack.WRITE_BLOCKING));
- mPosition += bytes /
- (mTrack.getChannelCount()
- * AudioFormat.getBytesPerSample(mTrack.getAudioFormat()));
+ final int result = mTrack.write(copy, bytes, AudioTrack.WRITE_NON_BLOCKING);
+ mTrackPosition += result / (mTrack.getChannelCount()
+ * AudioFormat.getBytesPerSample(mTrack.getAudioFormat()));
}
return bytes;
}
@@ -632,10 +633,9 @@
// which does check position and limit.
ByteBuffer copy = audioBuffer.duplicate();
copy.position(0).limit(bytes); // read places data at the start of the buffer.
- Assert.assertEquals(bytes, mTrack.write(copy, bytes, AudioTrack.WRITE_BLOCKING));
- mPosition += bytes /
- (mTrack.getChannelCount()
- * AudioFormat.getBytesPerSample(mTrack.getAudioFormat()));
+ final int result = mTrack.write(copy, bytes, AudioTrack.WRITE_NON_BLOCKING);
+ mTrackPosition += result / (mTrack.getChannelCount()
+ * AudioFormat.getBytesPerSample(mTrack.getAudioFormat()));
}
return bytes;
}
@@ -652,11 +652,11 @@
public void stop() {
super.stop();
if (mTrack != null) {
- if (mPosition > 0) { // stop may be called multiple times.
- final int remainingFrames = mPosition - mTrack.getPlaybackHeadPosition();
+ if (mTrackPosition > 0) { // stop may be called multiple times.
+ final int remainingFrames = mTrackPosition - mTrack.getPlaybackHeadPosition();
mFinishAtMs = System.currentTimeMillis()
+ remainingFrames * 1000 / mTrack.getSampleRate();
- mPosition = 0;
+ mTrackPosition = 0;
}
mTrack.stop(); // allows remaining data to play out
}
@@ -681,7 +681,7 @@
public AudioTrack mTrack;
private final static String TAG = "AudioRecordAudit";
- private int mPosition;
+ private int mTrackPosition;
private long mFinishAtMs;
}
}
diff --git a/tests/tests/media/common/src/android/media/cts/CodecState.java b/tests/tests/media/common/src/android/media/cts/CodecState.java
index 1c08259..e117c3b 100644
--- a/tests/tests/media/common/src/android/media/cts/CodecState.java
+++ b/tests/tests/media/common/src/android/media/cts/CodecState.java
@@ -70,6 +70,7 @@
private long mFirstSampleTimeUs;
private long mPlaybackStartTimeUs;
private long mLastPresentTimeUs;
+ private long mOffsetTimestampUs = 0;
private MediaCodec mCodec;
private MediaTimeProvider mMediaTimeProvider;
private MediaExtractor mExtractor;
@@ -194,7 +195,7 @@
public long getCurrentPositionUs() {
// Use decoded frame time when available, otherwise default to render time (typically, in
// tunnel mode).
- if (mDecodedFramePresentationTimeUs > 0) {
+ if (mDecodedFramePresentationTimeUs != UNINITIALIZED_TIMESTAMP) {
return mDecodedFramePresentationTimeUs;
} else {
return mRenderedVideoFramePresentationTimeUs;
@@ -364,7 +365,7 @@
sampleTime -= mFirstSampleTimeUs;
}
- mLastPresentTimeUs = mPlaybackStartTimeUs + sampleTime;
+ mLastPresentTimeUs = mPlaybackStartTimeUs + sampleTime + mOffsetTimestampUs;
if ((sampleFlags & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0) {
MediaCodec.CryptoInfo info = new MediaCodec.CryptoInfo();
@@ -655,4 +656,25 @@
}
return null;
}
+
+ /**
+ * Seek media extractor to the beginning of the configured track.
+ *
+ * @param shouldContinuePts a boolean that controls whether timestamps keep increasing
+ */
+ public void seekToBeginning(boolean shouldContinuePts) {
+ mExtractor.seekTo(UNINITIALIZED_TIMESTAMP, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
+ if (shouldContinuePts) {
+ if (mDecodedFramePresentationTimeUs != UNINITIALIZED_TIMESTAMP) {
+ mOffsetTimestampUs = mDecodedFramePresentationTimeUs;
+ return;
+ }
+ if (mRenderedVideoFramePresentationTimeUs != UNINITIALIZED_TIMESTAMP) {
+ mOffsetTimestampUs = mRenderedVideoFramePresentationTimeUs;
+ return;
+ }
+ } else {
+ mOffsetTimestampUs = 0;
+ }
+ }
}
diff --git a/tests/tests/media/common/src/android/media/cts/MediaCodecTunneledPlayer.java b/tests/tests/media/common/src/android/media/cts/MediaCodecTunneledPlayer.java
index ef371da..a602a13 100644
--- a/tests/tests/media/common/src/android/media/cts/MediaCodecTunneledPlayer.java
+++ b/tests/tests/media/common/src/android/media/cts/MediaCodecTunneledPlayer.java
@@ -41,9 +41,13 @@
public class MediaCodecTunneledPlayer implements MediaTimeProvider {
private static final String TAG = MediaCodecTunneledPlayer.class.getSimpleName();
+ /** State the player starts in, before configuration. */
private static final int STATE_IDLE = 1;
+ /** State of the player during initial configuration. */
private static final int STATE_PREPARING = 2;
+ /** State of the player during playback. */
private static final int STATE_PLAYING = 3;
+ /** State of the player when configured but not playing. */
private static final int STATE_PAUSED = 4;
private Boolean mThreadStarted = false;
@@ -398,12 +402,14 @@
/**
* Flushes all the video codecs when the player is in stand-by.
+ *
+ * @throws IllegalStateException if the player is not paused
*/
public void videoFlush() {
Log.d(TAG, "videoFlush");
synchronized (mState) {
- if (mState == STATE_PLAYING || mState == STATE_PREPARING) {
- return;
+ if (mState != STATE_PAUSED) {
+ throw new IllegalStateException("Expected STATE_PAUSED, got " + mState);
}
for (CodecState state : mVideoCodecStates.values()) {
@@ -412,6 +418,24 @@
}
}
+ /** Seek all video tracks to their very beginning.
+ *
+ * @param shouldContinuePts a boolean that controls whether timestamps keep increasing
+ * @throws IllegalStateException if the player is not paused
+ */
+ public void videoSeekToBeginning(boolean shouldContinuePts) {
+ Log.d(TAG, "videoSeekToBeginning");
+ synchronized (mState) {
+ if (mState != STATE_PAUSED) {
+ throw new IllegalStateException("Expected STATE_PAUSED, got " + mState);
+ }
+
+ for (CodecState state : mVideoCodecStates.values()) {
+ state.seekToBeginning(shouldContinuePts);
+ }
+ }
+ }
+
/**
* Enables or disables looping. Should be called after {@link #prepare()}.
*/
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTest.java
index 1593193..5405b32 100644
--- a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTest.java
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTest.java
@@ -59,7 +59,6 @@
import android.media.cts.Preconditions;
import android.media.cts.SdkMediaCodec;
import android.net.Uri;
-import android.os.ParcelFileDescriptor;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -4348,9 +4347,13 @@
mMediaCodecPlayer.getCurrentPosition());
mMediaCodecPlayer.pause();
Thread.sleep(50);
- assertTrue("Video is ahead of audio", mMediaCodecPlayer.getCurrentPosition() <=
- mMediaCodecPlayer.getAudioTrackPositionUs());
+ final long audioPositionUs = mMediaCodecPlayer.getAudioTrackPositionUs();
+ final long videoPositionUs = mMediaCodecPlayer.getCurrentPosition();
+ assertTrue(String.format("Video pts (%d) is ahead of audio pts (%d)",
+ videoPositionUs, audioPositionUs),
+ videoPositionUs <= audioPositionUs);
mMediaCodecPlayer.videoFlush();
+ mMediaCodecPlayer.videoSeekToBeginning(true /* shouldContinuePts */);
Thread.sleep(50);
assertEquals("Video frame rendered after flush", CodecState.UNINITIALIZED_TIMESTAMP,
mMediaCodecPlayer.getCurrentPosition());
diff --git a/tests/tests/media/drm/Android.bp b/tests/tests/media/drm/Android.bp
index 63facc3a..05545db 100644
--- a/tests/tests/media/drm/Android.bp
+++ b/tests/tests/media/drm/Android.bp
@@ -65,6 +65,7 @@
"mts-media",
],
host_required: ["cts-dynamic-config"],
- min_sdk_version: "29",
- target_sdk_version: "31",
+ // TODO(b/208938664) Change this sdk version suppression to T once it's defined to number (33).
+ min_sdk_version: "current",
+ target_sdk_version: "current",
}
diff --git a/tests/tests/media/drm/AndroidTest.xml b/tests/tests/media/drm/AndroidTest.xml
index 5715237..6a4fb73 100644
--- a/tests/tests/media/drm/AndroidTest.xml
+++ b/tests/tests/media/drm/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
<option name="target" value="host" />
<option name="config-filename" value="CtsMediaDrmTestCases" />
diff --git a/tests/tests/media/drm/OWNERS b/tests/tests/media/drm/OWNERS
index 4e95edc..09b0051 100644
--- a/tests/tests/media/drm/OWNERS
+++ b/tests/tests/media/drm/OWNERS
@@ -1,10 +1,13 @@
# Bug component: 1344
# android-drm-team
+conglin@google.com
edwinwong@google.com
fredgc@google.com
jtinker@google.com
+juce@google.com
kylealexander@google.com
mattfedd@google.com
rfrias@google.com
robertshih@google.com
sigquit@google.com
+vickymin@google.com
diff --git a/tests/tests/media/drm/jni/Android.bp b/tests/tests/media/drm/jni/Android.bp
index 0dc55fa..3a755e5 100644
--- a/tests/tests/media/drm/jni/Android.bp
+++ b/tests/tests/media/drm/jni/Android.bp
@@ -42,5 +42,6 @@
],
stl: "libc++_static",
gtest: false,
- sdk_version: "29",
+ // TODO(b/208938664) Change this sdk version suppression to T once it's defined to number (33).
+ sdk_version: "current",
}
diff --git a/tests/tests/media/drm/jni/native-mediadrm-jni.cpp b/tests/tests/media/drm/jni/native-mediadrm-jni.cpp
index e0433ba..441d17a 100644
--- a/tests/tests/media/drm/jni/native-mediadrm-jni.cpp
+++ b/tests/tests/media/drm/jni/native-mediadrm-jni.cpp
@@ -62,7 +62,10 @@
static const char kFileScheme[] = "file://";
static constexpr size_t kFileSchemeStrLen = sizeof(kFileScheme) - 1;
-static constexpr size_t kPlayTimeSeconds = 30;
+// Test time must be under 30 seconds to meet CTS quality bar.
+// The first ten seconds in kPlayTimeSeconds plays the clear lead,
+// the next ten seconds verifies encrypted playback.
+static constexpr size_t kPlayTimeSeconds = 20;
static constexpr size_t kUuidSize = 16;
static const uint8_t kClearKeyUuid[kUuidSize] = {
@@ -103,6 +106,8 @@
0x72, 0x79, 0x22, 0x7d
};
+static const char kDefaultUrl[] = "https://default.url";
+
static const size_t kKeyRequestSize = sizeof(kKeyRequestData);
// base 64 encoded JSON response string, must not contain padding character '='
@@ -970,6 +975,87 @@
return JNI_TRUE;
}
+extern "C" jboolean Java_android_mediadrm_cts_NativeMediaDrmClearkeyTest_testGetKeyRequestNative(
+ JNIEnv* env, jclass /*clazz*/, jbyteArray uuid, jobject playbackParams) {
+
+ if (NULL == uuid || NULL == playbackParams) {
+ jniThrowException(env, "java/lang/NullPointerException",
+ "null uuid or null playback parameters");
+ return JNI_FALSE;
+ }
+
+ Uuid juuid = jbyteArrayToUuid(env, uuid);
+ if (!isUuidSizeValid(juuid)) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "invalid UUID size, expected %u bytes", kUuidSize);
+ return JNI_FALSE;
+ }
+
+ PlaybackParams params;
+ initPlaybackParams(env, playbackParams, params);
+
+ AMediaObjects aMediaObjects;
+ media_status_t status = AMEDIA_OK;
+ aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
+ if (NULL == aMediaObjects.getDrm()) {
+ jniThrowException(env, "java/lang/RuntimeException", "null MediaDrm");
+ return JNI_FALSE;
+ }
+
+ AMediaDrmSessionId sessionId;
+ status = AMediaDrm_openSession(aMediaObjects.getDrm(), &sessionId);
+ if (status != AMEDIA_OK) {
+ jniThrowException(env, "java/lang/RuntimeException",
+ "openSession failed");
+ return JNI_FALSE;
+ }
+
+ // Pointer to keyRequest memory, which remains until the next
+ // AMediaDrm_getKeyRequest call or until the drm object is released.
+ const uint8_t* keyRequest;
+ size_t keyRequestSize = 0;
+ std::string errorMessage;
+
+ const char *defaultUrl;
+ AMediaDrmKeyRequestType keyRequestType;
+
+ // The server recognizes "video/mp4" but not "video/avc".
+ status = AMediaDrm_getKeyRequestWithDefaultUrlAndType(aMediaObjects.getDrm(),
+ &sessionId, kClearkeyPssh, sizeof(kClearkeyPssh),
+ "video/mp4" /*mimeType*/, KEY_TYPE_STREAMING,
+ NULL, 0, &keyRequest, &keyRequestSize, &defaultUrl, &keyRequestType);
+
+ if(status != AMEDIA_OK) return JNI_FALSE;
+
+ switch(keyRequestType) {
+ case KEY_REQUEST_TYPE_INITIAL:
+ case KEY_REQUEST_TYPE_RENEWAL:
+ case KEY_REQUEST_TYPE_RELEASE:
+ case KEY_REQUEST_TYPE_NONE:
+ case KEY_REQUEST_TYPE_UPDATE:
+ break;
+ default:
+ errorMessage.assign("keyRequestType returned is [%d], error = %d");
+ AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+ jniThrowExceptionFmt(env, "java/lang/RuntimeException", errorMessage.c_str(), keyRequestType, status);
+ return JNI_FALSE;
+ }
+
+ ALOGD("kDefaultUrl [%s], length %d, defaultUrl [%s], length %d",
+ kDefaultUrl,
+ (int)strlen(kDefaultUrl),
+ defaultUrl,
+ (int)strlen(defaultUrl));
+
+ if (strlen(kDefaultUrl) != strlen(defaultUrl) || strcmp(kDefaultUrl, defaultUrl) != 0) {
+ AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+ jniThrowExceptionFmt(env, "java/lang/RuntimeException", "Default Url is not correct [%s], error = %d", defaultUrl, status);
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
static JNINativeMethod gMethods[] = {
{ "isCryptoSchemeSupportedNative", "([B)Z",
(void *)Java_android_mediadrm_cts_NativeMediaDrmClearkeyTest_isCryptoSchemeSupportedNative },
@@ -994,6 +1080,10 @@
{ "testFindSessionIdNative", "([B)Z",
(void *)Java_android_mediadrm_cts_NativeMediaDrmClearkeyTest_testFindSessionIdNative },
+
+ { "testGetKeyRequestNative",
+ "([BLandroid/media/drm/cts/NativeMediaDrmClearkeyTest$PlaybackParams;)Z",
+ (void *)Java_android_mediadrm_cts_NativeMediaDrmClearkeyTest_testGetKeyRequestNative},
};
int register_android_mediadrm_cts_NativeMediaDrmClearkeyTest(JNIEnv* env) {
diff --git a/tests/tests/media/drm/src/android/media/drm/cts/MediaPlayerDrmTestBase.java b/tests/tests/media/drm/src/android/media/drm/cts/MediaPlayerDrmTestBase.java
index 5445692..80d6fb8 100644
--- a/tests/tests/media/drm/src/android/media/drm/cts/MediaPlayerDrmTestBase.java
+++ b/tests/tests/media/drm/src/android/media/drm/cts/MediaPlayerDrmTestBase.java
@@ -137,7 +137,12 @@
private static final String TAG = "MediaPlayerDrmTestBase";
- protected static final int PLAY_TIME_MS = 60 * 1000;
+ // PLAY_TIME_MS dictates the total run time of the playback
+ // tests. To meet the under 30 seconds CTS quality bar,
+ // the first ten seconds plays the clear lead,
+ // the next ten seconds verifies encrypted playback.
+ // This applies to both streaming and offline tests.
+ protected static final int PLAY_TIME_MS = 20 * 1000;
protected byte[] mKeySetId;
protected boolean mAudioOnly;
diff --git a/tests/tests/media/drm/src/android/media/drm/cts/NativeMediaDrmClearkeyTest.java b/tests/tests/media/drm/src/android/media/drm/cts/NativeMediaDrmClearkeyTest.java
index de21b7f..8f6f874 100644
--- a/tests/tests/media/drm/src/android/media/drm/cts/NativeMediaDrmClearkeyTest.java
+++ b/tests/tests/media/drm/src/android/media/drm/cts/NativeMediaDrmClearkeyTest.java
@@ -22,9 +22,13 @@
import android.media.cts.MediaCodecBlockModelHelper;
import android.media.cts.Utils;
import android.net.Uri;
+import android.os.Build;
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.AppModeFull;
import android.util.Log;
import android.view.Surface;
+import androidx.test.filters.SdkSuppress;
import com.android.compatibility.common.util.ApiLevelUtil;
import com.android.compatibility.common.util.MediaUtils;
@@ -127,6 +131,7 @@
return buffer.array();
}
+ @Presubmit
public void testIsCryptoSchemeSupported() throws Exception {
if (watchHasNoClearkeySupport()) {
return;
@@ -136,16 +141,19 @@
assertTrue(isCryptoSchemeSupportedNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
}
+ @Presubmit
public void testIsCryptoSchemeNotSupported() throws Exception {
assertFalse(isCryptoSchemeSupportedNative(uuidByteArray(BAD_SCHEME_UUID)));
}
+ @Presubmit
public void testPssh() throws Exception {
// The test uses a canned PSSH that contains the common box UUID.
assertTrue(testPsshNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID),
Uri.parse(Utils.getMediaPath() + CENC_CLEARKEY_VIDEO_PATH).toString()));
}
+ @Presubmit
public void testQueryKeyStatus() throws Exception {
if (watchHasNoClearkeySupport()) {
return;
@@ -154,6 +162,7 @@
assertTrue(testQueryKeyStatusNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
}
+ @Presubmit
public void testFindSessionId() throws Exception {
if (watchHasNoClearkeySupport()) {
return;
@@ -162,6 +171,7 @@
assertTrue(testFindSessionIdNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
}
+ @Presubmit
public void testGetPropertyString() throws Exception {
if (watchHasNoClearkeySupport()) {
return;
@@ -176,6 +186,7 @@
assertEquals("ClearKey CDM", value.toString());
}
+ @Presubmit
public void testPropertyByteArray() throws Exception {
if (watchHasNoClearkeySupport()) {
return;
@@ -184,6 +195,7 @@
assertTrue(testPropertyByteArrayNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
}
+ @Presubmit
public void testUnknownPropertyString() throws Exception {
StringBuffer value = new StringBuffer();
@@ -288,6 +300,9 @@
private static native boolean testQueryKeyStatusNative(final byte[] uuid);
+ private static native boolean testGetKeyRequestNative(final byte[] uuid,
+ PlaybackParams params);
+
public void testClearKeyPlaybackCenc() throws Exception {
testClearKeyPlayback(
COMMON_PSSH_SCHEME_UUID,
@@ -297,6 +312,8 @@
VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
}
+ @FlakyTest(bugId = 173646795)
+ @Presubmit
public void testClearKeyPlaybackCenc2() throws Exception {
testClearKeyPlayback(
CLEARKEY_SCHEME_UUID,
@@ -305,5 +322,20 @@
Uri.parse(Utils.getMediaPath() + CENC_CLEARKEY_VIDEO_PATH),
VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
}
+
+ // TODO(b/208938664) Change this sdk version suppression to T once it's defined to number (33).
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S_V2)
+ public void testClearKeyGetKeyRequest() throws Exception {
+ PlaybackParams params = new PlaybackParams();
+ params.surface = mActivity.getSurfaceHolder().getSurface();
+ params.mimeType = ISO_BMFF_VIDEO_MIME_TYPE;
+ params.audioUrl = Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH).toString();
+ params.videoUrl = Uri.parse(Utils.getMediaPath() + CENC_CLEARKEY_VIDEO_PATH).toString();
+ boolean status = testGetKeyRequestNative(
+ uuidByteArray(CLEARKEY_SCHEME_UUID),
+ params);
+ assertTrue(status);
+ params.surface.release();
+ }
}
diff --git a/tests/tests/media/muxer/Android.bp b/tests/tests/media/muxer/Android.bp
index 3eb38a6..f252315 100644
--- a/tests/tests/media/muxer/Android.bp
+++ b/tests/tests/media/muxer/Android.bp
@@ -44,8 +44,9 @@
// include both the 32 and 64 bit versions
compile_multilib: "both",
static_libs: [
- "ctstestrunner-axt",
"cts-media-common",
+ "ctstestrunner-axt",
+ "exoplayer2-metadataretriever-mediamuxer",
],
jni_libs: [
"libctsmediamuxertest_jni",
diff --git a/tests/tests/media/muxer/src/android/media/muxer/cts/MediaMuxerTest.java b/tests/tests/media/muxer/src/android/media/muxer/cts/MediaMuxerTest.java
index bf69401..6105dda 100644
--- a/tests/tests/media/muxer/src/android/media/muxer/cts/MediaMuxerTest.java
+++ b/tests/tests/media/muxer/src/android/media/muxer/cts/MediaMuxerTest.java
@@ -16,6 +16,8 @@
package android.media.muxer.cts;
+import static org.junit.Assert.assertArrayEquals;
+
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.MediaCodec.BufferInfo;
@@ -32,6 +34,13 @@
import com.android.compatibility.common.util.MediaUtils;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.MetadataRetriever;
+import com.google.android.exoplayer2.source.TrackGroupArray;
+import com.google.android.exoplayer2.util.Util;
+import com.google.android.exoplayer2.video.ColorInfo;
+
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -39,6 +48,8 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Vector;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
import java.util.stream.IntStream;
@AppModeFull(reason = "No interaction with system server")
@@ -336,6 +347,75 @@
checkTimestampsWithStartOffsets(startOffsetUsVect);
}
+ public void testAdditionOfHdrStaticMetadata() throws Exception {
+ String outputFilePath =
+ File.createTempFile("MediaMuxerTest_testAdditionOfHdrStaticMetadata", ".mp4")
+ .getAbsolutePath();
+ // HDR static metadata encoding the following information (format defined in CTA-861.3 -
+ // Static Metadata Descriptor, includes descriptor ID):
+ // Mastering display color primaries:
+ // R: x=0.677980 y=0.321980, G: x=0.245000 y=0.703000, B: x=0.137980 y=0.052000,
+ // White point: x=0.312680 y=0.328980
+ // Mastering display luminance min: 0.0000 cd/m2, max: 1000 cd/m2
+ // Maximum Content Light Level: 1100 cd/m2
+ // Maximum Frame-Average Light Level: 180 cd/m2
+ byte[] inputHdrStaticMetadata =
+ Util.getBytesFromHexString("006b84e33eda2f4e89f31a280a123d4140e80300004c04b400");
+ Function<MediaFormat, MediaFormat> staticMetadataAdditionFunction =
+ (mediaFormat) -> {
+ if (!mediaFormat.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+ return mediaFormat;
+ }
+ MediaFormat result = new MediaFormat(mediaFormat);
+ result.setByteBuffer(
+ MediaFormat.KEY_HDR_STATIC_INFO,
+ ByteBuffer.wrap(inputHdrStaticMetadata));
+ return result;
+ };
+ try {
+ cloneMediaUsingMuxer(
+ /* srcMedia= */ "video_h264_main_b_frames.mp4",
+ outputFilePath,
+ /* expectedTrackCount= */ 2,
+ /* degrees= */ 0,
+ MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,
+ staticMetadataAdditionFunction);
+ assertArrayEquals(
+ inputHdrStaticMetadata, getVideoColorInfo(outputFilePath).hdrStaticInfo);
+ } finally {
+ new File(outputFilePath).delete();
+ }
+ }
+
+ public void testAdditionOfInvalidHdrStaticMetadataIsIgnored() throws Exception {
+ String outputFilePath =
+ File.createTempFile(
+ "MediaMuxerTest_testAdditionOfInvalidHdrStaticMetadataIsIgnored",
+ ".mp4")
+ .getAbsolutePath();
+ Function<MediaFormat, MediaFormat> staticMetadataAdditionFunction =
+ (mediaFormat) -> {
+ MediaFormat result = new MediaFormat(mediaFormat);
+ // The input static info should be ignored, because its size is invalid (26 vs
+ // expected 25).
+ result.setByteBuffer(
+ MediaFormat.KEY_HDR_STATIC_INFO, ByteBuffer.allocateDirect(26));
+ return result;
+ };
+ try {
+ cloneMediaUsingMuxer(
+ /* srcMedia= */ "video_h264_main_b_frames.mp4",
+ outputFilePath,
+ /* expectedTrackCount= */ 2,
+ /* degrees= */ 0,
+ MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,
+ staticMetadataAdditionFunction);
+ assertNull(getVideoColorInfo(outputFilePath));
+ } finally {
+ new File(outputFilePath).delete();
+ }
+ }
+
/**
* Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
* when video and audio samples start after different times.
@@ -430,8 +510,13 @@
private void cloneAndVerify(final String srcMedia, String outputMediaFile,
int expectedTrackCount, int degrees, int fmt) throws IOException {
try {
- cloneMediaUsingMuxer(srcMedia, outputMediaFile, expectedTrackCount,
- degrees, fmt);
+ cloneMediaUsingMuxer(
+ srcMedia,
+ outputMediaFile,
+ expectedTrackCount,
+ degrees,
+ fmt,
+ Function.identity());
if (fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 ||
fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) {
verifyAttributesMatch(srcMedia, outputMediaFile, degrees);
@@ -445,11 +530,14 @@
}
}
- /**
- * Using the MediaMuxer to clone a media file.
- */
- private void cloneMediaUsingMuxer(final String srcMedia, String dstMediaPath,
- int expectedTrackCount, int degrees, int fmt)
+ /** Using the MediaMuxer to clone a media file. */
+ private void cloneMediaUsingMuxer(
+ final String srcMedia,
+ String dstMediaPath,
+ int expectedTrackCount,
+ int degrees,
+ int fmt,
+ Function<MediaFormat, MediaFormat> muxerInputTrackFormatTransformer)
throws IOException {
// Set up MediaExtractor to read from the source.
AssetFileDescriptor srcFd = getAssetFileDescriptorFor(srcMedia);
@@ -469,7 +557,7 @@
for (int i = 0; i < trackCount; i++) {
extractor.selectTrack(i);
MediaFormat format = extractor.getTrackFormat(i);
- int dstIndex = muxer.addTrack(format);
+ int dstIndex = muxer.addTrack(muxerInputTrackFormatTransformer.apply(format));
indexMap.put(i, dstIndex);
}
@@ -594,7 +682,7 @@
testFd.close();
}
- private void verifyLocationInFile(String fileName) {
+ private void verifyLocationInFile(String fileName) throws IOException {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(fileName);
String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION);
@@ -1165,5 +1253,19 @@
fail("either audio track has not reached its last sample");
}
}
+
+ /** Returns the static HDR metadata in the given {@code file}, or null if not present. */
+ private ColorInfo getVideoColorInfo(String path)
+ throws ExecutionException, InterruptedException {
+ TrackGroupArray trackGroupArray =
+ MetadataRetriever.retrieveMetadata(getContext(), MediaItem.fromUri(path)).get();
+ for (int i = 0; i < trackGroupArray.length; i++) {
+ Format format = trackGroupArray.get(i).getFormat(0);
+ if (format.sampleMimeType.startsWith("video/")) {
+ return format.colorInfo;
+ }
+ }
+ return null;
+ }
}
diff --git a/tests/tests/media/player/src/android/media/player/cts/MediaPlayerTest.java b/tests/tests/media/player/src/android/media/player/cts/MediaPlayerTest.java
index b530f66..b46366c 100644
--- a/tests/tests/media/player/src/android/media/player/cts/MediaPlayerTest.java
+++ b/tests/tests/media/player/src/android/media/player/cts/MediaPlayerTest.java
@@ -76,6 +76,7 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
@@ -1182,7 +1183,7 @@
playVideoTest(file, displayWidth, displayHeight);
}
- private void checkVideoRotationAngle(int angle, String file) {
+ private void checkVideoRotationAngle(int angle, String file) throws IOException {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(file);
String rotation = retriever.extractMetadata(
diff --git a/tests/tests/media/recorder/src/android/media/recorder/cts/MediaRecorderTest.java b/tests/tests/media/recorder/src/android/media/recorder/cts/MediaRecorderTest.java
index 5049978..ffc2dcf 100644
--- a/tests/tests/media/recorder/src/android/media/recorder/cts/MediaRecorderTest.java
+++ b/tests/tests/media/recorder/src/android/media/recorder/cts/MediaRecorderTest.java
@@ -514,7 +514,7 @@
retriever = null;
}
- private boolean checkLocationInFile(String fileName) {
+ private boolean checkLocationInFile(String fileName) throws IOException {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(fileName);
String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION);
diff --git a/tests/tests/media/src/android/media/cts/HeifWriterTest.java b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
index 40d6128..7694d4c 100644
--- a/tests/tests/media/src/android/media/cts/HeifWriterTest.java
+++ b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
@@ -419,7 +419,11 @@
}
mWidth = mBitmaps[0].getWidth();
mHeight = mBitmaps[0].getHeight();
- retriever.release();
+ try {
+ retriever.release();
+ } catch (IOException e) {
+ // Ignoring errors occurred while releasing the MediaMetadataRetriever.
+ }
}
private void cleanupStaleOutputs() {
diff --git a/tests/tests/media/src/android/media/cts/MediaActivityTest.java b/tests/tests/media/src/android/media/cts/MediaActivityTest.java
index 8cbe255..f38dada 100644
--- a/tests/tests/media/src/android/media/cts/MediaActivityTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaActivityTest.java
@@ -16,17 +16,19 @@
package android.media.cts;
-import static junit.framework.Assert.assertEquals;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertFalse;
+import static org.junit.Assert.fail;
+import android.Manifest;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Resources;
+import android.hardware.hdmi.HdmiControlManager;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.session.MediaSession;
@@ -84,17 +86,26 @@
private Map<Integer, Integer> mStreamVolumeMap = new HashMap<>();
private MediaSession mSession;
+ private HdmiControlManager mHdmiControlManager;
+ private int mHdmiEnableStatus;
+
@Rule
public ActivityTestRule<MediaSessionTestActivity> mActivityRule =
new ActivityTestRule<>(MediaSessionTestActivity.class, false, false);
@Before
public void setUp() throws Exception {
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.HDMI_CEC);
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mContext = mInstrumentation.getContext();
- mUseFixedVolume = mContext.getResources().getBoolean(
- Resources.getSystem().getIdentifier("config_useFixedVolume", "bool", "android"));
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mUseFixedVolume = mAudioManager.isVolumeFixed();
+ mHdmiControlManager = mContext.getSystemService(HdmiControlManager.class);
+ if(mHdmiControlManager != null) {
+ mHdmiEnableStatus = mHdmiControlManager.getHdmiCecEnabled();
+ mHdmiControlManager.setHdmiCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+ }
mStreamVolumeMap.clear();
for (Integer stream : ALL_VOLUME_STREAMS) {
@@ -133,6 +144,9 @@
mSession.release();
mSession = null;
}
+ if (mHdmiControlManager != null) {
+ mHdmiControlManager.setHdmiCecEnabled(mHdmiEnableStatus);
+ }
try {
mActivityRule.finishActivity();
@@ -157,6 +171,7 @@
@Test
public void testVolumeKey_whileSessionAlive() throws Exception {
if (mUseFixedVolume) {
+ Log.i(TAG, "testVolumeKey_whileSessionAlive skipped due to full volume device");
return;
}
@@ -185,6 +200,7 @@
@Test
public void testVolumeKey_afterSessionReleased() throws Exception {
if (mUseFixedVolume) {
+ Log.i(TAG, "testVolumeKey_afterSessionReleased skipped due to full volume device");
return;
}
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index 20d0f50..762ed74 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -153,6 +153,37 @@
return ds;
}
+ public void testExceptionWhileClosingMediaDataSource() throws IOException {
+ MediaDataSource backingMediaDataSource =
+ TestMediaDataSource.fromAssetFd(
+ getAssetFileDescriptorFor("audio_with_metadata.mp3"));
+ MediaDataSource mediaDataSource = new MediaDataSource() {
+ @Override
+ public int readAt(long position, byte[] buffer, int offset, int size)
+ throws IOException {
+ return backingMediaDataSource.readAt(position, buffer, offset, size);
+ }
+
+ @Override
+ public long getSize() throws IOException {
+ return backingMediaDataSource.getSize();
+ }
+
+ @Override
+ public void close() throws IOException {
+ backingMediaDataSource.close();
+ throw new IOException();
+ }
+ };
+ mRetriever.setDataSource(mediaDataSource);
+ try {
+ mRetriever.release();
+ fail("Expected IOException not thrown.");
+ } catch (IOException e) {
+ // Expected.
+ }
+ }
+
public void testAudioMetadata() {
setDataSourceCallback("audio_with_metadata.mp3");
@@ -387,13 +418,6 @@
}
public void testID3v240ExtHeader() {
- if(!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R)) {
- // The fix for b/154357105 was released in mainline release 30.09.007.01
- // See https://android-build.googleplex.com/builds/treetop/googleplex-android-review/11174063
- if (TestUtils.skipTestIfMainlineLessThan("com.google.android.media", 300900701)) {
- return;
- }
- }
setDataSourceFd("sinesweepid3v24ext.mp3");
assertEquals("Mime type was other than expected",
"audio/mpeg",
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
index 5508dfa..61c2ddb 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
@@ -38,6 +38,7 @@
import android.provider.Settings;
import android.test.InstrumentationTestCase;
import android.test.UiThreadTest;
+import android.text.TextUtils;
import android.view.KeyEvent;
import com.android.compatibility.common.util.ApiLevelUtil;
@@ -579,6 +580,7 @@
}
}
+ @NonMediaMainlineTest
public void testIsTrustedForMediaControl_withInvalidUid() throws Exception {
List<String> packageNames = getEnabledNotificationListenerPackages();
for (String packageName : packageNames) {
@@ -627,7 +629,7 @@
Settings.Secure.getString(
mContext.getContentResolver(),
ENABLED_NOTIFICATION_LISTENERS);
- if (enabledNotificationListeners != null) {
+ if (!TextUtils.isEmpty(enabledNotificationListeners)) {
String[] components = enabledNotificationListeners.split(":");
for (String componentString : components) {
ComponentName component = ComponentName.unflattenFromString(componentString);
diff --git a/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java b/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
index 3623f15..84c4684 100644
--- a/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
+++ b/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
@@ -113,7 +113,8 @@
mContext = InstrumentationRegistry.getTargetContext();
mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.MEDIA_CONTENT_CONTROL,
- Manifest.permission.QUERY_AUDIO_STATE);
+ Manifest.permission.MODIFY_AUDIO_ROUTING,
+ Manifest.permission.QUERY_AUDIO_STATE);
mExecutor = Executors.newSingleThreadExecutor();
mAudioManager = (AudioManager) mContext.getSystemService(AUDIO_SERVICE);
@@ -1023,7 +1024,7 @@
private void releaseAllSessions() {
MediaRouter2Manager manager = MediaRouter2Manager.getInstance(mContext);
- for (RoutingSessionInfo session : manager.getActiveSessions()) {
+ for (RoutingSessionInfo session : manager.getRemoteSessions()) {
manager.releaseSession(session);
}
}
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
index d5d01c8..0df3b1b 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
@@ -162,6 +162,8 @@
}
}
+ Preconditions.assertTestFileExists(mediaName);
+
File playbackOutput = new File(WorkDir.getTopDir(), "PlaybackTestResult.txt");
Writer output = new BufferedWriter(new FileWriter(playbackOutput, true));
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java b/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java
new file mode 100644
index 0000000..6fc2b8a
--- /dev/null
+++ b/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediastress.cts;
+
+import java.io.File;
+
+import junit.framework.Assert;
+
+/**
+ * Static methods used to validate preconditions in the media CTS suite
+ * to simplify failure diagnosis.
+ */
+
+public final class Preconditions {
+ private static final String TAG = "Preconditions";
+
+ public static void assertTestFileExists(String pathName) {
+ File testFile = new File(pathName);
+ Assert.assertTrue("Test Setup Error, missing file: " + pathName, testFile.exists());
+ }
+
+ private Preconditions() {}
+}
diff --git a/tests/tests/mediatranscoding/Android.bp b/tests/tests/mediatranscoding/Android.bp
index 0b889ae..6a220a3 100644
--- a/tests/tests/mediatranscoding/Android.bp
+++ b/tests/tests/mediatranscoding/Android.bp
@@ -19,10 +19,12 @@
android_test {
name: "CtsMediaTranscodingTestCases",
defaults: ["CtsMediaTranscodingTestCasesDefaults", "cts_defaults"],
- min_sdk_version: "31",
+ // part of MTS, so we need compatibility back to Q/29
+ min_sdk_version: "29",
test_suites: [
"cts",
"general-tests",
+ "mts-media",
"mts",
],
static_libs: [
@@ -43,5 +45,4 @@
"android.test.base",
"android.test.runner",
],
- sdk_version: "test_current",
}
diff --git a/tests/tests/mediatranscoding/AndroidManifest.xml b/tests/tests/mediatranscoding/AndroidManifest.xml
index 1618b00..c69754a 100644
--- a/tests/tests/mediatranscoding/AndroidManifest.xml
+++ b/tests/tests/mediatranscoding/AndroidManifest.xml
@@ -25,6 +25,12 @@
<uses-library android:name="android.test.runner" />
</application>
+ <!-- included in mainline testing, so must run back on 29.
+ Transcoding was introduced in 31 -->
+ <uses-sdk android:minSdkVersion="29"
+ android:targetSdkVersion="31"/>
+
+
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="android.media.mediatranscoding.cts"
android:label="Tests for MediaTranscoding.">
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
index ba40ca5..6e63624 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
@@ -27,6 +27,8 @@
import android.test.AndroidTestCase;
import android.util.Xml;
+import androidx.test.filters.SdkSuppress;
+
import org.junit.Test;
import org.xmlpull.v1.XmlPullParser;
@@ -36,6 +38,7 @@
@Presubmit
@AppModeFull(reason = "Instant apps cannot access the SD card")
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
public class ApplicationMediaCapabilitiesTest extends AndroidTestCase {
private static final String TAG = "ApplicationMediaCapabilitiesTest";
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
index 78e7b33..fd99ed0 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
@@ -33,6 +33,7 @@
import android.media.MediaTranscodingManager.TranscodingSession;
import android.media.MediaTranscodingManager.VideoTranscodingRequest;
import android.net.Uri;
+// import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
@@ -44,6 +45,7 @@
import android.test.AndroidTestCase;
import android.util.Log;
+import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.MediaUtils;
@@ -70,6 +72,7 @@
@Presubmit
@RequiresDevice
@AppModeFull(reason = "Instant apps cannot access the SD card")
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
public class MediaTranscodingManagerTest extends AndroidTestCase {
private static final String TAG = "MediaTranscodingManagerTest";
/** The time to wait for the transcode operation to complete before failing the test. */
@@ -195,6 +198,7 @@
// Skip the test for TV, Car and Watch devices.
private boolean shouldSkip() {
+
PackageManager pm =
InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
return pm.hasSystemFeature(pm.FEATURE_LEANBACK) || pm.hasSystemFeature(pm.FEATURE_WATCH)
diff --git a/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java b/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
index 4ed2853..8d359bd 100644
--- a/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
+++ b/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
@@ -18,25 +18,24 @@
import android.content.Context;
import android.content.pm.PackageManager;
-import android.media.midi.MidiManager;
-import android.media.midi.MidiOutputPort;
import android.media.midi.MidiDevice;
-import android.media.midi.MidiDevice.MidiConnection;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiDeviceInfo.PortInfo;
import android.media.midi.MidiDeviceStatus;
import android.media.midi.MidiInputPort;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiOutputPort;
import android.media.midi.MidiReceiver;
-import android.media.midi.MidiSender;
import android.os.Bundle;
-import android.util.Log;
import android.test.AndroidTestCase;
+import android.util.Log;
import com.android.midi.CTSMidiEchoTestService;
import com.android.midi.MidiEchoTestService;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Random;
/**
@@ -307,6 +306,8 @@
assertEquals("MIDI device type", MidiDeviceInfo.TYPE_VIRTUAL,
echoInfo.getType());
+ assertEquals("MIDI default protocol", MidiDeviceInfo.PROTOCOL_UNKNOWN,
+ echoInfo.getDefaultProtocol());
}
// Is the MidiManager supported?
@@ -324,6 +325,14 @@
MidiDeviceInfo[] infos = midiManager.getDevices();
assertTrue("device list was null", infos != null);
assertTrue("device list was empty", infos.length >= 1);
+
+ Collection<MidiDeviceInfo> legacyDeviceInfos = midiManager.getDevicesForTransport(
+ MidiManager.TRANSPORT_MIDI_BYTE_STREAM);
+ assertTrue("Legacy Device list was null.", legacyDeviceInfos != null);
+ assertTrue("Legacy Device list was empty", legacyDeviceInfos.size() >= 1);
+ Collection<MidiDeviceInfo> universalDeviceInfos = midiManager.getDevicesForTransport(
+ MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS);
+ assertTrue("Universal Device list was null.", universalDeviceInfos != null);
}
public void testDeviceInfo() throws Exception {
diff --git a/tests/tests/multiuser/Android.bp b/tests/tests/multiuser/Android.bp
index c930a43..2ffc0f2 100644
--- a/tests/tests/multiuser/Android.bp
+++ b/tests/tests/multiuser/Android.bp
@@ -28,6 +28,9 @@
static_libs: [
"ctstestrunner-axt",
"compatibility-device-util-axt",
+ "Nene",
+ "Harrier",
+ "TestApp",
],
libs: ["android.test.base"],
sdk_version: "test_current",
diff --git a/tests/tests/multiuser/AndroidManifest.xml b/tests/tests/multiuser/AndroidManifest.xml
index 17a71d7..b660662 100644
--- a/tests/tests/multiuser/AndroidManifest.xml
+++ b/tests/tests/multiuser/AndroidManifest.xml
@@ -19,8 +19,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.multiuser.cts"
android:targetSandboxVersion="2">
- <!-- Must be at least 30, otherwise some tests could be invalid. -->
- <uses-sdk android:targetSdkVersion="30"/>
+ <!-- Must be at least 33, otherwise some tests could be invalid. -->
+ <uses-sdk android:targetSdkVersion="33"/>
<application>
<uses-library android:name="android.test.runner" />
diff --git a/tests/tests/multiuser/AndroidTest.xml b/tests/tests/multiuser/AndroidTest.xml
index df4a765..5286139 100644
--- a/tests/tests/multiuser/AndroidTest.xml
+++ b/tests/tests/multiuser/AndroidTest.xml
@@ -20,6 +20,7 @@
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="multiuser" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsMultiUserTestCases.apk" />
@@ -27,5 +28,7 @@
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.multiuser.cts" />
<option name="runtime-hint" value="8m" />
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
</test>
</configuration>
diff --git a/tests/tests/multiuser/src/android/multiuser/cts/NewUserRequestTest.java b/tests/tests/multiuser/src/android/multiuser/cts/NewUserRequestTest.java
new file mode 100644
index 0000000..ccabd9d
--- /dev/null
+++ b/tests/tests/multiuser/src/android/multiuser/cts/NewUserRequestTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.multiuser.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.NewUserRequest;
+import org.junit.Test;
+
+public class NewUserRequestTest {
+
+ @Test
+ public void testSetName() {
+ String name = "testUser";
+ NewUserRequest request = new NewUserRequest.Builder().setName(name).build();
+
+ assertThat(request.getName()).isEqualTo(name);
+ }
+
+ @Test
+ public void testSetNameNull() {
+ NewUserRequest request = new NewUserRequest.Builder().setName(null).build();
+
+ assertThat(request.getName()).isNull();
+ }
+
+ @Test
+ public void testSetAdmin() {
+ NewUserRequest request = new NewUserRequest.Builder().setAdmin().build();
+
+ assertThat(request.isAdmin()).isTrue();
+ }
+
+ @Test
+ public void testBuildThrowsOnNullUserType() {
+ assertThrows(IllegalStateException.class,
+ () -> new NewUserRequest.Builder().setUserType(null).setAdmin().build());
+ }
+}
diff --git a/tests/tests/multiuser/src/android/multiuser/cts/NewUserResponseTest.java b/tests/tests/multiuser/src/android/multiuser/cts/NewUserResponseTest.java
new file mode 100644
index 0000000..65447d3
--- /dev/null
+++ b/tests/tests/multiuser/src/android/multiuser/cts/NewUserResponseTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.multiuser.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.NewUserResponse;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import org.junit.Test;
+
+public final class NewUserResponseTest {
+
+ @Test
+ public void testNewUserResponseSuccessful() {
+ UserHandle userHandle = UserHandle.of(100);
+
+ NewUserResponse response =
+ new NewUserResponse(userHandle, UserManager.USER_OPERATION_SUCCESS);
+
+ assertThat(response.isSuccessful()).isTrue();
+ assertThat(response.getUser()).isEqualTo(userHandle);
+ assertThat(response.getOperationResult()).isEqualTo(UserManager.USER_OPERATION_SUCCESS);
+ }
+
+ @Test
+ public void testNewUserResponseUnsuccessful() {
+ NewUserResponse response = new NewUserResponse(/* user= */ null,
+ UserManager.USER_OPERATION_ERROR_UNKNOWN);
+
+ assertThat(response.isSuccessful()).isFalse();
+ assertThat(response.getUser()).isNull();
+ assertThat(response.getOperationResult())
+ .isEqualTo(UserManager.USER_OPERATION_ERROR_UNKNOWN);
+ }
+}
diff --git a/tests/tests/multiuser/src/android/multiuser/cts/PermissionHelper.java b/tests/tests/multiuser/src/android/multiuser/cts/PermissionHelper.java
new file mode 100644
index 0000000..5211498
--- /dev/null
+++ b/tests/tests/multiuser/src/android/multiuser/cts/PermissionHelper.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.multiuser.cts;
+
+import android.app.Instrumentation;
+
+public class PermissionHelper implements AutoCloseable {
+ private final Instrumentation mInstrumentation;
+
+ private PermissionHelper(Instrumentation instrumentation, String... permissions) {
+ if (instrumentation == null) {
+ throw new IllegalArgumentException("instrumentation must not be null");
+ }
+ mInstrumentation = instrumentation;
+ mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(permissions);
+ }
+
+ @Override
+ public void close() {
+ mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+ }
+
+ public static PermissionHelper adoptShellPermissionIdentity(
+ Instrumentation instrumentation, String... permissions) {
+ return new PermissionHelper(instrumentation, permissions);
+ }
+}
diff --git a/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java b/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
index 78e0619..ff01b59 100644
--- a/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
+++ b/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
@@ -16,46 +16,85 @@
package android.multiuser.cts;
+import static android.Manifest.permission.CREATE_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.content.pm.PackageManager.FEATURE_MANAGED_USERS;
+import static android.multiuser.cts.PermissionHelper.adoptShellPermissionIdentity;
import static android.multiuser.cts.TestingUtils.getBooleanProperty;
+import static android.os.UserManager.USER_OPERATION_SUCCESS;
+import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-import android.Manifest;
+import static org.junit.Assume.assumeTrue;
+
import android.app.Instrumentation;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.os.Build;
+import android.os.NewUserRequest;
+import android.os.NewUserResponse;
+import android.os.PersistableBundle;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.SystemUserOnly;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasPermission;
+import com.android.bedstead.harrier.annotations.RequireFeature;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.users.UserType;
+
import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
-@RunWith(JUnit4.class)
+@RunWith(BedsteadJUnit4.class)
public final class UserManagerTest {
- private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
- private final Context mContext = mInstrumentation.getContext();
+ @ClassRule
+ @Rule
+ public static final DeviceState sDeviceState = new DeviceState();
+ private static final Context sContext = TestApis.context().instrumentedContext();
+
+ private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
private UserManager mUserManager;
- @Before
- public void setTestFixtures() {
- mUserManager = mContext.getSystemService(UserManager.class);
+ private final String mAccountName = "test_account_name";
+ private final String mAccountType = "test_account_type";
+ @Before
+ public void setUp() {
+ mUserManager = sContext.getSystemService(UserManager.class);
assertWithMessage("UserManager service").that(mUserManager).isNotNull();
}
+ private void removeUser(UserHandle userHandle) {
+ if (userHandle == null) {
+ return;
+ }
+
+ try (PermissionHelper ph = adoptShellPermissionIdentity(mInstrumentation, CREATE_USERS)) {
+ assertThat(mUserManager.removeUser(userHandle)).isTrue();
+ }
+ }
+
/**
* Verify that the isUserAGoat() method always returns false for API level 30. This is
* because apps targeting R no longer have access to package queries by default.
@@ -84,30 +123,174 @@
@Test
@SystemUserOnly(reason = "Profiles are only supported on system user.")
public void testCloneUser() throws Exception {
- // Need CREATE_USERS permission to create user in test
- mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
- Manifest.permission.CREATE_USERS, Manifest.permission.INTERACT_ACROSS_USERS);
- Set<String> disallowedPackages = new HashSet<String>();
- UserHandle userHandle = mUserManager.createProfile(
- "Clone user", UserManager.USER_TYPE_PROFILE_CLONE, disallowedPackages);
- assertThat(userHandle).isNotNull();
+ UserHandle userHandle = null;
- try {
- final Context userContext = mContext.createPackageContextAsUser("system", 0,
+ // Need CREATE_USERS permission to create user in test
+ try (PermissionHelper ph = adoptShellPermissionIdentity(mInstrumentation, CREATE_USERS)) {
+ Set<String> disallowedPackages = new HashSet<String>();
+ userHandle = mUserManager.createProfile(
+ "Clone user", UserManager.USER_TYPE_PROFILE_CLONE, disallowedPackages);
+ assertThat(userHandle).isNotNull();
+
+ final Context userContext = sContext.createPackageContextAsUser("system", 0,
userHandle);
final UserManager cloneUserManager = userContext.getSystemService(UserManager.class);
assertThat(cloneUserManager.isMediaSharedWithParent()).isTrue();
assertThat(cloneUserManager.isCloneProfile()).isTrue();
List<UserInfo> list = mUserManager.getUsers(true, true, true);
+ final UserHandle finalUserHandle = userHandle;
List<UserInfo> cloneUsers = list.stream().filter(
- user -> (user.id == userHandle.getIdentifier()
+ user -> (user.id == finalUserHandle.getIdentifier()
&& user.isCloneProfile()))
.collect(Collectors.toList());
assertThat(cloneUsers.size()).isEqualTo(1);
} finally {
- assertThat(mUserManager.removeUser(userHandle)).isTrue();
- mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+ removeUser(userHandle);
}
}
+
+ @Test
+ @SystemUserOnly(reason = "Restricted users are only supported on system user.")
+ public void testRestrictedUser() throws Exception {
+ UserHandle user = null;
+ try (PermissionHelper ph = adoptShellPermissionIdentity(mInstrumentation, CREATE_USERS)) {
+ // Check that the SYSTEM user is not restricted.
+ assertThat(mUserManager.isRestrictedProfile()).isFalse();
+ assertThat(mUserManager.isRestrictedProfile(UserHandle.SYSTEM)).isFalse();
+ assertThat(mUserManager.getRestrictedProfileParent()).isNull();
+
+ final UserInfo info = mUserManager.createRestrictedProfile("Restricted user");
+
+ // If the device supports Restricted users, it must report it correctly.
+ assumeTrue("Couldn't create a restricted profile", info != null);
+
+ user = UserHandle.of(info.id);
+ assertThat(mUserManager.isRestrictedProfile(user)).isTrue();
+
+ final Context userContext = sContext.createPackageContextAsUser("system", 0, user);
+ final UserManager userUm = userContext.getSystemService(UserManager.class);
+ // TODO(183239043): Remove the if{} clause after v33 Sdk bump.
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S_V2) {
+ assertThat(userUm.isRestrictedProfile()).isTrue();
+ }
+ assertThat(userUm.getRestrictedProfileParent().isSystem()).isTrue();
+ } finally {
+ removeUser(user);
+ }
+ }
+
+ private NewUserRequest newUserRequest() {
+ final PersistableBundle accountOptions = new PersistableBundle();
+ accountOptions.putString("test_account_option_key", "test_account_option_value");
+
+ return new NewUserRequest.Builder()
+ .setName("test_user")
+ .setUserType(USER_TYPE_FULL_SECONDARY)
+ .setUserIcon(Bitmap.createBitmap(32, 32, Bitmap.Config.RGB_565))
+ .setAccountName(mAccountName)
+ .setAccountType(mAccountType)
+ .setAccountOptions(accountOptions)
+ .build();
+ }
+
+ @Test
+ public void testSomeUserHasAccount() {
+ UserHandle user = null;
+
+ try (PermissionHelper ph = adoptShellPermissionIdentity(mInstrumentation, CREATE_USERS)) {
+ assertThat(mUserManager.someUserHasAccount(mAccountName, mAccountType)).isFalse();
+ user = mUserManager.createUser(newUserRequest()).getUser();
+ assertThat(mUserManager.someUserHasAccount(mAccountName, mAccountType)).isTrue();
+ } finally {
+ removeUser(user);
+ }
+ }
+
+ @Test
+ public void testSomeUserHasAccount_shouldIgnoreToBeRemovedUsers() {
+ try (PermissionHelper ph = adoptShellPermissionIdentity(mInstrumentation, CREATE_USERS)) {
+ final NewUserResponse response = mUserManager.createUser(newUserRequest());
+ assertThat(response.getOperationResult()).isEqualTo(USER_OPERATION_SUCCESS);
+ mUserManager.removeUser(response.getUser());
+ assertThat(mUserManager.someUserHasAccount(mAccountName, mAccountType)).isFalse();
+ }
+ }
+
+ @Test
+ public void testCreateUser_withNewUserRequest_shouldCreateUserWithCorrectProperties()
+ throws PackageManager.NameNotFoundException {
+ UserHandle user = null;
+
+ try (PermissionHelper ph = adoptShellPermissionIdentity(mInstrumentation, CREATE_USERS)) {
+ final NewUserRequest request = newUserRequest();
+ final NewUserResponse response = mUserManager.createUser(request);
+ user = response.getUser();
+
+ assertThat(response.getOperationResult()).isEqualTo(USER_OPERATION_SUCCESS);
+ assertThat(response.isSuccessful()).isTrue();
+ assertThat(user).isNotNull();
+
+ UserManager userManagerOfNewUser = sContext
+ .createPackageContextAsUser("android", 0, user)
+ .getSystemService(UserManager.class);
+
+ assertThat(userManagerOfNewUser.getUserName()).isEqualTo(request.getName());
+ assertThat(userManagerOfNewUser.getUserType()).isEqualTo(request.getUserType());
+ // We can not test userIcon and accountOptions,
+ // because getters require MANAGE_USERS permission.
+ // And we are already testing accountName and accountType
+ // are set correctly in testSomeUserHasAccount method.
+ } finally {
+ removeUser(user);
+ }
+ }
+
+ @Test
+ public void testCreateUser_withNewUserRequest_shouldNotAllowDuplicateUserAccounts() {
+ UserHandle user1 = null;
+ UserHandle user2 = null;
+
+ try (PermissionHelper ph = adoptShellPermissionIdentity(mInstrumentation, CREATE_USERS)) {
+ final NewUserResponse response1 = mUserManager.createUser(newUserRequest());
+ user1 = response1.getUser();
+
+ assertThat(response1.getOperationResult()).isEqualTo(USER_OPERATION_SUCCESS);
+ assertThat(response1.isSuccessful()).isTrue();
+ assertThat(user1).isNotNull();
+
+ final NewUserResponse response2 = mUserManager.createUser(newUserRequest());
+ user2 = response2.getUser();
+
+ assertThat(response2.getOperationResult()).isEqualTo(
+ UserManager.USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS);
+ assertThat(response2.isSuccessful()).isFalse();
+ assertThat(user2).isNull();
+ } finally {
+ removeUser(user1);
+ removeUser(user2);
+ }
+ }
+
+ @Test
+ @AppModeFull
+ @RequireFeature(FEATURE_MANAGED_USERS)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS)
+ public void getProfileParent_withNewlyCreatedProfile() {
+ final UserReference parent = TestApis.users().instrumented();
+ try (UserReference profile = TestApis.users().createUser()
+ .parent(parent)
+ .type(TestApis.users().supportedType(UserType.MANAGED_PROFILE_TYPE_NAME))
+ .createAndStart()) {
+ assertThat(mUserManager.getProfileParent(profile.userHandle()))
+ .isEqualTo(parent.userHandle());
+ }
+ }
+
+ @Test
+ @AppModeFull
+ @EnsureHasPermission(INTERACT_ACROSS_USERS)
+ public void getProfileParent_returnsNullForNonProfile() {
+ assertThat(mUserManager.getProfileParent(TestApis.users().system().userHandle())).isNull();
+ }
}
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio_callback.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_callback.cpp
index 1b8cf44..78e009b 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio_callback.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio_callback.cpp
@@ -142,6 +142,27 @@
}
};
+ void createAndVerifyHonoringMMap() {
+ aaudio_policy_t originalPolicy = AAUDIO_POLICY_AUTO;
+
+ // Turn off MMap if requested.
+ bool allowMMap = std::get<PARAM_ALLOW_MMAP>(GetParam()) == MMAP_ALLOWED;
+ if (AAudioExtensions::getInstance().isMMapSupported()) {
+ originalPolicy = AAudioExtensions::getInstance().getMMapPolicy();
+ AAudioExtensions::getInstance().setMMapEnabled(allowMMap);
+ }
+
+ mHelper->createAndVerifyStream(&mSetupSuccessful);
+
+ // Restore policy for next test.
+ if (AAudioExtensions::getInstance().isMMapSupported()) {
+ AAudioExtensions::getInstance().setMMapPolicy(originalPolicy);
+ }
+ if (!allowMMap) {
+ ASSERT_FALSE(AAudioExtensions::getInstance().isMMapUsed(mHelper->stream()));
+ }
+ }
+
static void MyErrorCallbackProc(AAudioStream *stream, void *userData, aaudio_result_t error);
AAudioStreamBuilder* builder() const { return mHelper->builder(); }
@@ -149,7 +170,7 @@
const StreamBuilderHelper::Parameters& actual() const { return mHelper->actual(); }
std::unique_ptr<T> mHelper;
- bool mSetupSuccesful = false;
+ bool mSetupSuccessful = false;
std::unique_ptr<AAudioCallbackTestData> mCbData;
};
@@ -179,9 +200,8 @@
}
void AAudioInputStreamCallbackTest::SetUp() {
- aaudio_policy_t originalPolicy = AAUDIO_POLICY_AUTO;
- mSetupSuccesful = false;
+ mSetupSuccessful = false;
if (!deviceSupportsFeature(FEATURE_RECORDING)) return;
mHelper.reset(new InputStreamBuilderHelper(
std::get<PARAM_SHARING_MODE>(GetParam()),
@@ -198,28 +218,12 @@
AAudioStreamBuilder_setFramesPerDataCallback(builder(), framesPerDataCallback);
}
- // Turn off MMap if requested.
- int allowMMap = std::get<PARAM_ALLOW_MMAP>(GetParam()) == MMAP_ALLOWED;
- if (AAudioExtensions::getInstance().isMMapSupported()) {
- originalPolicy = AAudioExtensions::getInstance().getMMapPolicy();
- AAudioExtensions::getInstance().setMMapEnabled(allowMMap);
- }
-
- mHelper->createAndVerifyStream(&mSetupSuccesful);
-
- // Restore policy for next test.
- if (AAudioExtensions::getInstance().isMMapSupported()) {
- AAudioExtensions::getInstance().setMMapPolicy(originalPolicy);
- }
- if (!allowMMap) {
- ASSERT_FALSE(AAudioExtensions::getInstance().isMMapUsed(mHelper->stream()));
- }
-
+ createAndVerifyHonoringMMap();
}
// Test starting and stopping an INPUT AAudioStream that uses a Callback
TEST_P(AAudioInputStreamCallbackTest, testRecording) {
- if (!mSetupSuccesful) return;
+ if (!mSetupSuccessful) return;
const int32_t framesPerDataCallback = std::get<PARAM_FRAMES_PER_CB>(GetParam());
const int32_t streamFramesPerDataCallback = AAudioStream_getFramesPerDataCallback(stream());
@@ -346,7 +350,7 @@
}
void AAudioOutputStreamCallbackTest::SetUp() {
- mSetupSuccesful = false;
+ mSetupSuccessful = false;
if (!deviceSupportsFeature(FEATURE_PLAYBACK)) return;
mHelper.reset(new OutputStreamBuilderHelper(
std::get<PARAM_SHARING_MODE>(GetParam()),
@@ -363,13 +367,13 @@
AAudioStreamBuilder_setFramesPerDataCallback(builder(), framesPerDataCallback);
}
- mHelper->createAndVerifyStream(&mSetupSuccesful);
+ createAndVerifyHonoringMMap();
}
// Test starting and stopping an OUTPUT AAudioStream that uses a Callback
TEST_P(AAudioOutputStreamCallbackTest, testPlayback) {
- if (!mSetupSuccesful) return;
+ if (!mSetupSuccessful) return;
const int32_t framesPerDataCallback = std::get<PARAM_FRAMES_PER_CB>(GetParam());
const int32_t streamFramesPerDataCallback = AAudioStream_getFramesPerDataCallback(stream());
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
index cc811cd..047419b 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
@@ -82,6 +82,7 @@
static void try_opening_audio_stream(AAudioStreamBuilder *aaudioBuilder, Expect expect) {
// Create an AAudioStream using the Builder.
AAudioStream *aaudioStream = nullptr;
+ int64_t beforeTimeNanos = getNanoseconds();
aaudio_result_t result = AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream);
if (expect == Expect::FAIL) {
ASSERT_NE(AAUDIO_OK, result);
@@ -94,10 +95,19 @@
|| ((result == AAUDIO_OK) && (aaudioStream != nullptr)));
}
+ // The stream should be open within one second.
+ static const int64_t kNanosPerSecond = 1e9;
+ ASSERT_LT(getNanoseconds() - beforeTimeNanos, kNanosPerSecond)
+ << "It took more than one second to open stream";
+
// Cleanup
ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder));
if (aaudioStream != nullptr) {
+ beforeTimeNanos = getNanoseconds();
ASSERT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
+ // The stream should be closed within one second.
+ ASSERT_LT(getNanoseconds() - beforeTimeNanos, kNanosPerSecond)
+ << "It took more than one second to close stream";
}
}
@@ -624,3 +634,67 @@
std::make_pair(AAUDIO_DIRECTION_INPUT, AAUDIO_CHANNEL_5POINT1),
std::make_pair(AAUDIO_DIRECTION_INPUT, AAUDIO_UNSPECIFIED)),
&AAudioStreamBuilderChannelMaskAndCountTest::getTestName);
+
+using CommonCombinationTestParams = std::tuple<aaudio_direction_t,
+ aaudio_sharing_mode_t,
+ aaudio_performance_mode_t,
+ int32_t /*sample rate*/,
+ aaudio_format_t,
+ aaudio_channel_mask_t>;
+enum {
+ PARAM_DIRECTION = 0,
+ PARAM_SHARING_MODE,
+ PARAM_PERFORMANCE_MODE,
+ PARAM_SAMPLE_RATE,
+ PARAM_FORMAT,
+ PARAM_CHANNEL_MASK
+};
+class AAudioStreamBuilderCommonCombinationTest :
+ public ::testing::TestWithParam<CommonCombinationTestParams> {
+ public:
+ static std::string getTestName(
+ const ::testing::TestParamInfo<CommonCombinationTestParams>& info) {
+ std::stringstream ss;
+ ss << (std::get<PARAM_DIRECTION>(info.param) == AAUDIO_DIRECTION_INPUT ? "INPUT_"
+ : "OUTPUT_")
+ << sharingModeToString(std::get<PARAM_SHARING_MODE>(info.param)) << "_"
+ << performanceModeToString(std::get<PARAM_PERFORMANCE_MODE>(info.param)) << "_"
+ << "sampleRate_" << std::get<PARAM_SAMPLE_RATE>(info.param) << "_"
+ << "format_0x" << std::hex << std::get<PARAM_FORMAT>(info.param) << "_"
+ << "channelMask_0x" << std::get<PARAM_CHANNEL_MASK>(info.param) << "";
+ return ss.str();
+ }
+};
+
+TEST_P(AAudioStreamBuilderCommonCombinationTest, openStream) {
+ if (!deviceSupportsFeature(FEATURE_PLAYBACK)) return;
+ AAudioStreamBuilder *aaudioBuilder = nullptr;
+ create_stream_builder(&aaudioBuilder);
+ const auto param = GetParam();
+ AAudioStreamBuilder_setDirection(aaudioBuilder, std::get<PARAM_DIRECTION>(param));
+ AAudioStreamBuilder_setSharingMode(aaudioBuilder, std::get<PARAM_SHARING_MODE>(param));
+ AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, std::get<PARAM_PERFORMANCE_MODE>(param));
+ AAudioStreamBuilder_setSampleRate(aaudioBuilder, std::get<PARAM_SAMPLE_RATE>(param));
+ AAudioStreamBuilder_setFormat(aaudioBuilder, std::get<PARAM_FORMAT>(param));
+ AAudioStreamBuilder_setChannelMask(aaudioBuilder, std::get<PARAM_CHANNEL_MASK>(param));
+ // All the test parameters all reasonable values with different combination. In that case,
+ // it is expected that the opening will be successful.
+ try_opening_audio_stream(aaudioBuilder, Expect::SUCCEED);
+}
+
+INSTANTIATE_TEST_CASE_P(CommonComb, AAudioStreamBuilderCommonCombinationTest,
+ ::testing::Combine(
+ ::testing::Values(AAUDIO_DIRECTION_OUTPUT, AAUDIO_DIRECTION_INPUT),
+ ::testing::Values(AAUDIO_SHARING_MODE_SHARED, AAUDIO_SHARING_MODE_EXCLUSIVE),
+ ::testing::Values(
+ AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_PERFORMANCE_MODE_POWER_SAVING,
+ AAUDIO_PERFORMANCE_MODE_LOW_LATENCY),
+ ::testing::Values(// Sample rate
+ AAUDIO_UNSPECIFIED, 8000, 16000, 44100, 48000, 96000, 192000),
+ ::testing::Values(
+ AAUDIO_UNSPECIFIED,
+ AAUDIO_FORMAT_PCM_I16,
+ AAUDIO_FORMAT_PCM_FLOAT),
+ ::testing::Values(AAUDIO_CHANNEL_MONO, AAUDIO_CHANNEL_STEREO)),
+ &AAudioStreamBuilderCommonCombinationTest::getTestName);
diff --git a/tests/tests/nativemedia/aaudio/jni/utils.cpp b/tests/tests/nativemedia/aaudio/jni/utils.cpp
index a9ed984..ebe2f56 100644
--- a/tests/tests/nativemedia/aaudio/jni/utils.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/utils.cpp
@@ -214,13 +214,6 @@
AAudioStreamBuilder_setBufferCapacityInFrames(mBuilder, kBufferCapacityFrames);
}
-void OutputStreamBuilderHelper::createAndVerifyStream(bool *success) {
- StreamBuilderHelper::createAndVerifyStream(success);
- if (*success) {
- ASSERT_GE(AAudioStream_getBufferCapacityInFrames(mStream), kBufferCapacityFrames);
- }
-}
-
AAudioExtensions::AAudioExtensions()
: mMMapSupported(isPolicyEnabled(getMMapPolicyProperty()))
, mMMapExclusiveSupported(isPolicyEnabled(getIntegerProperty(
@@ -267,4 +260,5 @@
}
mFunctionsLoaded = true;
return mFunctionsLoaded;
-}
\ No newline at end of file
+}
+
diff --git a/tests/tests/nativemedia/aaudio/jni/utils.h b/tests/tests/nativemedia/aaudio/jni/utils.h
index 8294eca..5674496 100644
--- a/tests/tests/nativemedia/aaudio/jni/utils.h
+++ b/tests/tests/nativemedia/aaudio/jni/utils.h
@@ -115,7 +115,6 @@
aaudio_performance_mode_t requestedPerfMode,
aaudio_format_t requestedFormat = AAUDIO_FORMAT_PCM_I16);
void initBuilder();
- void createAndVerifyStream(bool *success);
private:
const int32_t kBufferCapacityFrames = 2000;
diff --git a/tests/tests/nativemedia/resourceobserver/OWNERS b/tests/tests/nativemedia/resourceobserver/OWNERS
index 5679184..167c365 100644
--- a/tests/tests/nativemedia/resourceobserver/OWNERS
+++ b/tests/tests/nativemedia/resourceobserver/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 1344
-chz@google.com
wonsik@google.com
lajos@google.com
diff --git a/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
index 960149b..d27bac5 100644
--- a/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
+++ b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
@@ -21,7 +21,6 @@
import android.media.midi.MidiDevice;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiManager;
-import android.os.Bundle;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -474,6 +473,20 @@
Assert.assertEquals("failed pure native latency test.", 0, result);
}
+ /**
+ * Checks that getDefaultProtocol returns a valid value.
+ */
+ @Test
+ public void test_J_GetDefaultProtocol() throws Exception {
+ if (!hasMidiSupport()) {
+ return;
+ }
+
+ int defaultProtocol = getDefaultProtocol(mTestContext);
+ Assert.assertEquals("default protocol incorrect.",
+ MidiDeviceInfo.PROTOCOL_UNKNOWN, defaultProtocol);
+ }
+
// Native Routines
public static native void initN();
@@ -507,4 +520,7 @@
// Pure Native Checks
public native int matchNativeMessages(long ctx);
public native int checkNativeLatency(long ctx, long maxLatencyNanos);
+
+ // AMidiDevice getters
+ public native int getDefaultProtocol(long ctx);
}
diff --git a/tests/tests/nativemidi/jni/native-lib.cpp b/tests/tests/nativemidi/jni/native-lib.cpp
index 5a2f0ca..00cfb08 100644
--- a/tests/tests/nativemidi/jni/native-lib.cpp
+++ b/tests/tests/nativemidi/jni/native-lib.cpp
@@ -544,4 +544,13 @@
return ((TestContext*)ctx)->checkInOutLatency(maxLatencyNanos);
}
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getDefaultProtocol(
+ JNIEnv*, jobject, jlong ctx) {
+
+ TestContext* context = (TestContext*)ctx;
+
+ return AMidiDevice_getDefaultProtocol(context->nativeDevice);
+}
+
+
} // extern "C"
diff --git a/tests/tests/notificationlegacy/notificationlegacy20/Android.bp b/tests/tests/notificationlegacy/notificationlegacy20/Android.bp
index affcee1..5a0d2ad 100644
--- a/tests/tests/notificationlegacy/notificationlegacy20/Android.bp
+++ b/tests/tests/notificationlegacy/notificationlegacy20/Android.bp
@@ -25,6 +25,8 @@
"ctstestrunner-axt",
"androidx.test.rules",
"junit",
+ "permission-test-util-lib",
+ "compatibility-device-util-axt",
],
libs: [
"android.test.runner",
diff --git a/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java b/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java
index 8d2931f..e5564e3 100644
--- a/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java
+++ b/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java
@@ -16,6 +16,10 @@
package android.app.notification.legacy20.cts;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
+import static android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS;
+
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertNotNull;
@@ -33,6 +37,9 @@
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
import android.provider.Telephony.Threads;
import android.service.notification.StatusBarNotification;
import android.util.Log;
@@ -40,6 +47,8 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.SystemUtil;
+
import junit.framework.Assert;
import org.junit.After;
@@ -68,6 +77,7 @@
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
+ PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS);
toggleListenerAccess(TestNotificationListener.getId(),
InstrumentationRegistry.getInstrumentation(), false);
mNotificationManager = (NotificationManager) mContext.getSystemService(
@@ -79,6 +89,15 @@
@After
public void tearDown() throws Exception {
+ // Use test API to prevent PermissionManager from killing the test process when revoking
+ // permission.
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mContext.getSystemService(PermissionManager.class)
+ .revokePostNotificationPermissionWithoutKillForTest(
+ mContext.getPackageName(),
+ Process.myUserHandle().getIdentifier()),
+ REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
+ REVOKE_RUNTIME_PERMISSIONS);
toggleListenerAccess(TestNotificationListener.getId(),
InstrumentationRegistry.getInstrumentation(), false);
Thread.sleep(500); // wait for listener to disconnect
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/Android.bp b/tests/tests/notificationlegacy/notificationlegacy27/Android.bp
index 144ceac..b6033e1 100644
--- a/tests/tests/notificationlegacy/notificationlegacy27/Android.bp
+++ b/tests/tests/notificationlegacy/notificationlegacy27/Android.bp
@@ -25,6 +25,8 @@
"ctstestrunner-axt",
"androidx.test.rules",
"junit",
+ "permission-test-util-lib",
+ "compatibility-device-util-axt",
],
libs: [
"android.test.runner",
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
index 642dc72..4fb4917 100644
--- a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
@@ -16,6 +16,7 @@
package android.app.notification.legacy.cts;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
@@ -28,17 +29,16 @@
import static org.junit.Assert.assertTrue;
-import android.app.ActivityManager;
import android.app.Instrumentation;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.UiAutomation;
-import android.content.pm.PackageManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.provider.Telephony.Threads;
@@ -75,6 +75,8 @@
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(POST_NOTIFICATIONS);
toggleListenerAccess(TestNotificationListener.getId(),
InstrumentationRegistry.getInstrumentation(), false);
toggleListenerAccess(SecondaryNotificationListener.getId(),
@@ -87,6 +89,8 @@
@After
public void tearDown() throws Exception {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
toggleListenerAccess(TestNotificationListener.getId(),
InstrumentationRegistry.getInstrumentation(), false);
toggleListenerAccess(SecondaryNotificationListener.getId(),
diff --git a/tests/tests/notificationlegacy/notificationlegacy28/Android.bp b/tests/tests/notificationlegacy/notificationlegacy28/Android.bp
index 6a52f46..794ecf4 100644
--- a/tests/tests/notificationlegacy/notificationlegacy28/Android.bp
+++ b/tests/tests/notificationlegacy/notificationlegacy28/Android.bp
@@ -25,6 +25,8 @@
"ctstestrunner-axt",
"androidx.test.rules",
"junit",
+ "permission-test-util-lib",
+ "compatibility-device-util-axt",
],
libs: [
"android.test.runner",
diff --git a/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java b/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
index cf8981d..828c356 100644
--- a/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
+++ b/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
@@ -16,6 +16,10 @@
package android.app.notification.legacy28.cts;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
+import static android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS;
+
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;
@@ -25,12 +29,18 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.os.Process;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -49,13 +59,26 @@
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
-
+ PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS);
mNotificationManager = (NotificationManager) mContext.getSystemService(
Context.NOTIFICATION_SERVICE);
mNotificationManager.createNotificationChannel(new NotificationChannel(
NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
}
+ @After
+ public void tearDown() throws Exception {
+ // Use test API to prevent PermissionManager from killing the test process when revoking
+ // permission.
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mContext.getSystemService(PermissionManager.class)
+ .revokePostNotificationPermissionWithoutKillForTest(
+ mContext.getPackageName(),
+ Process.myUserHandle().getIdentifier()),
+ REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
+ REVOKE_RUNTIME_PERMISSIONS);
+ }
+
@Test
public void testPostFullScreenIntent_noPermission() {
// No Full screen intent permission; but full screen intent should still be allowed
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/Android.bp b/tests/tests/notificationlegacy/notificationlegacy29/Android.bp
index 0d01e20..ca03efa 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/Android.bp
+++ b/tests/tests/notificationlegacy/notificationlegacy29/Android.bp
@@ -24,6 +24,8 @@
"ctstestrunner-axt",
"androidx.test.rules",
"junit",
+ "permission-test-util-lib",
+ "compatibility-device-util-axt",
],
libs: [
"android.test.runner",
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
index c02c091..bd470a7 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
@@ -16,6 +16,9 @@
package android.app.notification.legacy29.cts;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
+import static android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS;
import static android.service.notification.NotificationAssistantService.FEEDBACK_RATING;
import static junit.framework.Assert.assertEquals;
@@ -41,7 +44,10 @@
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.SystemClock;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
import android.provider.Telephony;
import android.service.notification.Adjustment;
import android.service.notification.NotificationAssistantService;
@@ -51,6 +57,8 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.SystemUtil;
+
import junit.framework.Assert;
import org.junit.After;
@@ -85,9 +93,10 @@
}
@Before
- public void setUp() throws IOException {
+ public void setUp() throws Exception {
mUi = InstrumentationRegistry.getInstrumentation().getUiAutomation();
mContext = InstrumentationRegistry.getContext();
+ PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS);
mNotificationManager = (NotificationManager) mContext.getSystemService(
Context.NOTIFICATION_SERVICE);
mNotificationManager.createNotificationChannel(new NotificationChannel(
@@ -97,7 +106,16 @@
}
@After
- public void tearDown() throws IOException {
+ public void tearDown() throws Exception {
+ // Use test API to prevent PermissionManager from killing the test process when revoking
+ // permission.
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mContext.getSystemService(PermissionManager.class)
+ .revokePostNotificationPermissionWithoutKillForTest(
+ mContext.getPackageName(),
+ Process.myUserHandle().getIdentifier()),
+ REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
+ REVOKE_RUNTIME_PERMISSIONS);
if (mNotificationListenerService != null) mNotificationListenerService.resetData();
toggleListenerAccess(false);
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
index c164656..0a2021b 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
@@ -16,6 +16,9 @@
package android.app.notification.legacy29.cts;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
+import static android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS;
import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS;
@@ -33,14 +36,20 @@
import android.content.Context;
import android.content.Intent;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.SystemUtil;
+
import junit.framework.Assert;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -63,13 +72,26 @@
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
-
+ PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS);
mNotificationManager = (NotificationManager) mContext.getSystemService(
Context.NOTIFICATION_SERVICE);
mNotificationManager.createNotificationChannel(new NotificationChannel(
NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
}
+ @After
+ public void tearDown() throws Exception {
+ // Use test API to prevent PermissionManager from killing the test process when revoking
+ // permission.
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mContext.getSystemService(PermissionManager.class)
+ .revokePostNotificationPermissionWithoutKillForTest(
+ mContext.getPackageName(),
+ Process.myUserHandle().getIdentifier()),
+ REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
+ REVOKE_RUNTIME_PERMISSIONS);
+ }
+
private void toggleNotificationPolicyAccess(String packageName,
Instrumentation instrumentation, boolean on) throws IOException {
diff --git a/tests/tests/os/src/android/os/cts/AppHibernationIntegrationTest.kt b/tests/tests/os/src/android/os/cts/AppHibernationIntegrationTest.kt
index 7311878..308c757 100644
--- a/tests/tests/os/src/android/os/cts/AppHibernationIntegrationTest.kt
+++ b/tests/tests/os/src/android/os/cts/AppHibernationIntegrationTest.kt
@@ -26,6 +26,7 @@
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
+import android.permission.PermissionControllerManager
import android.platform.test.annotations.AppModeFull
import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
import android.provider.Settings
@@ -43,6 +44,7 @@
import com.android.compatibility.common.util.SystemUtil
import com.android.compatibility.common.util.SystemUtil.eventually
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import com.android.compatibility.common.util.UiAutomatorUtils
import org.hamcrest.CoreMatchers
import org.hamcrest.Matchers
@@ -55,6 +57,8 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
/**
* Integration test for app hibernation.
@@ -65,6 +69,7 @@
companion object {
const val LOG_TAG = "AppHibernationIntegrationTest"
const val WAIT_TIME_MS = 1000L
+ const val TIMEOUT_TIME_MS = 5000L
const val MAX_SCROLL_ATTEMPTS = 3
const val TEST_UNUSED_THRESHOLD = 1L
@@ -74,6 +79,7 @@
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private lateinit var packageManager: PackageManager
+ private lateinit var permissionControllerManager: PermissionControllerManager
@get:Rule
val disableAnimationRule = DisableAnimationRule()
@@ -84,6 +90,8 @@
@Before
fun setup() {
packageManager = context.packageManager
+ permissionControllerManager =
+ context.getSystemService(PermissionControllerManager::class.java)!!
// Collapse notifications
assertThat(
@@ -160,6 +168,41 @@
}
}
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ fun testUnusedAppCount() {
+ withUnusedThresholdMs(TEST_UNUSED_THRESHOLD) {
+ withApp(APK_PATH_S_APP, APK_PACKAGE_NAME_S_APP) {
+ // Use app
+ startApp(APK_PACKAGE_NAME_S_APP)
+ leaveApp(APK_PACKAGE_NAME_S_APP)
+ killApp(APK_PACKAGE_NAME_S_APP)
+
+ // Wait for the unused threshold time to pass
+ Thread.sleep(TEST_UNUSED_THRESHOLD)
+
+ // Run job
+ runAppHibernationJob(context, LOG_TAG)
+
+ // Verify unused app count pulled correctly
+ val countDownLatch = CountDownLatch(1)
+ var unusedAppCount = -1
+ runWithShellPermissionIdentity {
+ permissionControllerManager.getUnusedAppCount({ r -> r.run() },
+ { res ->
+ unusedAppCount = res
+ countDownLatch.countDown()
+ })
+
+ assertTrue("Timed out waiting for unused app count",
+ countDownLatch.await(TIMEOUT_TIME_MS, TimeUnit.MILLISECONDS))
+ assertTrue("Expected non-zero unused app count but is $unusedAppCount",
+ unusedAppCount > 0)
+ }
+ }
+ }
+ }
+
@AppModeFull(reason = "Uses application details settings")
@Test
fun testAppInfo_RemovePermissionsAndFreeUpSpaceToggleExists() {
diff --git a/tests/tests/os/src/android/os/cts/BuildVersionTest.java b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
index 070cc44..2430c6b 100644
--- a/tests/tests/os/src/android/os/cts/BuildVersionTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
@@ -28,8 +28,8 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.util.HashSet;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
diff --git a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
index b0307f1..c10120e 100644
--- a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
+++ b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
@@ -32,7 +32,7 @@
import android.util.Size
import android.util.SizeF
import android.util.SparseArray
-import android.widget.TextView
+import android.widget.ListView
import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import androidx.test.uiautomator.By
@@ -48,7 +48,6 @@
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import com.android.compatibility.common.util.ThrowingSupplier
import com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject
-import com.android.compatibility.common.util.children
import com.android.compatibility.common.util.click
import java.io.Serializable
import org.hamcrest.CoreMatchers.containsString
@@ -77,6 +76,12 @@
const val SHELL_PACKAGE_NAME = "com.android.shell"
const val TEST_APP_PACKAGE_NAME = "android.os.cts.companiontestapp"
const val TEST_APP_APK_LOCATION = "/data/local/tmp/cts/os/CtsCompanionTestApp.apk"
+ const val CDM_UI_PACKAGE_NAME = "com.android.companiondevicemanager"
+
+ val DEVICE_LIST_ITEM_SELECTOR: BySelector = By.res(CDM_UI_PACKAGE_NAME, "list_item_device")
+ val DEVICE_LIST_SELECTOR: BySelector = By.pkg(CDM_UI_PACKAGE_NAME)
+ .clazz(ListView::class.java.name)
+ .hasChild(DEVICE_LIST_ITEM_SELECTOR)
}
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
@@ -172,16 +177,12 @@
uiDevice.waitForIdle()
click("Watch")
- val device = getEventually({
- click("Associate")
- waitFindNode(hasIdThat(containsString("device_list")),
- failMsg = "Test requires a discoverable bluetooth device nearby",
- timeoutMs = 9_000)
- .children
- .find { it.className == TextView::class.java.name }
- .assertNotNull { "Empty device list" }
- }, 90_000)
- device!!.click()
+ click("Associate")
+
+ uiDevice.wait(Until.findObject(DEVICE_LIST_SELECTOR), 20_000)
+ ?.findObject(DEVICE_LIST_ITEM_SELECTOR)
+ ?.click()
+ ?: throw AssertionError("Empty device list")
eventually {
assertThat(getAssociatedDevices(TEST_APP_PACKAGE_NAME), not(empty()))
@@ -210,17 +211,12 @@
uiDevice.waitAndFind(By.desc("name filter")).text = ""
uiDevice.waitForIdle()
- val deviceForAssociation = getEventually({
- click("Associate")
- waitFindNode(hasIdThat(containsString("device_list")),
- failMsg = "Test requires a discoverable bluetooth device nearby",
- timeoutMs = 5_000)
- .children
- .find { it.className == TextView::class.java.name }
- .assertNotNull { "Empty device list" }
- }, 60_000)
+ click("Associate")
- deviceForAssociation!!.click()
+ uiDevice.wait(Until.findObject(DEVICE_LIST_SELECTOR), 20_000)
+ ?.findObject(DEVICE_LIST_ITEM_SELECTOR)
+ ?.click()
+ ?: throw AssertionError("Empty device list")
waitForIdle()
diff --git a/tests/tests/os/src/android/os/cts/LocaleListTest.java b/tests/tests/os/src/android/os/cts/LocaleListTest.java
index 3d05332..84cff45 100644
--- a/tests/tests/os/src/android/os/cts/LocaleListTest.java
+++ b/tests/tests/os/src/android/os/cts/LocaleListTest.java
@@ -530,4 +530,40 @@
assertFalse(LocaleList.isPseudoLocale(ULocale.forLanguageTag("fr-CA")));
assertFalse(LocaleList.isPseudoLocale((ULocale) null));
}
+
+ public void testMatchesLanguageAndScript() {
+ assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("fr-Latn-FR"),
+ Locale.forLanguageTag("fr-Latn")));
+ assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-Hans-CN"),
+ Locale.forLanguageTag("zh-Hans")));
+ assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-Hant-TW"),
+ Locale.forLanguageTag("zh-Hant")));
+ assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("en-US"),
+ Locale.forLanguageTag("en-US")));
+ assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("en-US"),
+ Locale.forLanguageTag("en-CA")));
+ assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("ar-NA"),
+ Locale.forLanguageTag("ar-ZA")));
+ assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-CN"),
+ Locale.forLanguageTag("zh")));
+ assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-CN"),
+ Locale.forLanguageTag("zh-Hans")));
+ assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-TW"),
+ Locale.forLanguageTag("zh-Hant")));
+
+ assertFalse(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-Hant-TW"),
+ Locale.forLanguageTag("zh-Hans")));
+ assertFalse(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("en-XA"),
+ Locale.forLanguageTag("en-US")));
+ assertFalse(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("ar-YE"),
+ Locale.forLanguageTag("ar-XB")));
+ assertFalse(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("en-US"),
+ Locale.forLanguageTag("zh-TW")));
+ assertFalse(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-TW"),
+ Locale.forLanguageTag("zh")));
+ assertFalse(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-CN"),
+ Locale.forLanguageTag("zh-Hant")));
+ assertFalse(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-TW"),
+ Locale.forLanguageTag("zh-Hans")));
+ }
}
diff --git a/tests/tests/os/src/android/os/cts/ParcelTest.java b/tests/tests/os/src/android/os/cts/ParcelTest.java
index f4b1a06..f567daa 100644
--- a/tests/tests/os/src/android/os/cts/ParcelTest.java
+++ b/tests/tests/os/src/android/os/cts/ParcelTest.java
@@ -704,6 +704,43 @@
p.recycle();
}
+ public void testWriteBlob() {
+ Parcel p;
+
+ byte[] shortBytes = {(byte) 21};
+ // Create a byte array with 70 KiB to make sure it is large enough to be saved into Android
+ // Shared Memory. The native blob inplace limit is 16 KiB. Also make it larger than the
+ // IBinder.MAX_IPC_SIZE which is 64 KiB.
+ byte[] largeBytes = new byte[70 * 1024];
+ for (int i = 0; i < largeBytes.length; i++) {
+ largeBytes[i] = (byte) (i / Byte.MAX_VALUE);
+ }
+ // test write null
+ p = Parcel.obtain();
+ p.writeBlob(null, 0, 2);
+ p.setDataPosition(0);
+ byte[] outputBytes = p.readBlob();
+ assertNull(outputBytes);
+ p.recycle();
+
+ // test write short bytes
+ p = Parcel.obtain();
+ p.writeBlob(shortBytes, 0, 1);
+ p.setDataPosition(0);
+ assertEquals(shortBytes[0], p.readBlob()[0]);
+ p.recycle();
+
+ // test write large bytes
+ p = Parcel.obtain();
+ p.writeBlob(largeBytes, 0, largeBytes.length);
+ p.setDataPosition(0);
+ outputBytes = p.readBlob();
+ for (int i = 0; i < largeBytes.length; i++) {
+ assertEquals(largeBytes[i], outputBytes[i]);
+ }
+ p.recycle();
+ }
+
public void testWriteByteArray() {
Parcel p;
diff --git a/tests/tests/os/src/android/os/cts/ProcessTest.java b/tests/tests/os/src/android/os/cts/ProcessTest.java
index 2ca0fd0..909f147 100644
--- a/tests/tests/os/src/android/os/cts/ProcessTest.java
+++ b/tests/tests/os/src/android/os/cts/ProcessTest.java
@@ -25,6 +25,11 @@
import android.test.AndroidTestCase;
import android.util.Log;
+/**
+ * CTS for {@link android.os.Process}.
+ *
+ * We have more test in cts/tests/process/ too.
+ */
public class ProcessTest extends AndroidTestCase {
public static final int THREAD_PRIORITY_HIGHEST = -20;
diff --git a/tests/tests/os/src/android/os/cts/SharedMemoryFileDescriptorTest.java b/tests/tests/os/src/android/os/cts/SharedMemoryFileDescriptorTest.java
new file mode 100644
index 0000000..9668a61
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/SharedMemoryFileDescriptorTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.SharedMemory;
+import android.system.OsConstants;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+public final class SharedMemoryFileDescriptorTest {
+
+ private static final String TAG = SharedMemoryFileDescriptorTest.class.getSimpleName();
+
+ @Test
+ public void testCreation() throws Exception {
+ // setup
+ int memSize = 32 * 1024;
+ int bufSize = memSize / 4;
+ SharedMemory sharedMem1 = SharedMemory.create(/* name= */null, memSize);
+ try (ByteBufferSession buffer1 =
+ new ByteBufferSession(sharedMem1, /* offset= */ 0, bufSize)) {
+ buffer1.fillSequentialPattern();
+ }
+
+ Parcel p = Parcel.obtain();
+ sharedMem1.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ ParcelFileDescriptor fileDescriptor = p.readFileDescriptor();
+
+ // execution
+ SharedMemory sharedMem2 = SharedMemory.fromFileDescriptor(fileDescriptor);
+
+ // assertion
+ assertFalse(fileDescriptor.getFileDescriptor().valid());
+ try (ByteBufferSession buffer2 =
+ new ByteBufferSession(sharedMem2, /* offset= */ 0, bufSize)) {
+ assertTrue(buffer2.checkSequentialPattern());
+ }
+ }
+
+ @Test
+ public void testDuplication() throws Exception {
+ // setup
+ int memSize = 32 * 1024;
+ int bufSize = memSize / 4;
+ SharedMemory sharedMem1 = SharedMemory.create(/* name= */ null, memSize);
+ try (ByteBufferSession buffer1 =
+ new ByteBufferSession(sharedMem1, /* offset= */ 0, bufSize)) {
+ buffer1.fillSequentialPattern();
+ }
+
+ Parcel p = Parcel.obtain();
+ sharedMem1.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ ParcelFileDescriptor fileDescriptor = p.readFileDescriptor();
+ ParcelFileDescriptor dupFileDescriptor = fileDescriptor.dup();
+
+ // execution
+ SharedMemory sharedMem2 = SharedMemory.fromFileDescriptor(dupFileDescriptor);
+
+ // assertion
+ assertTrue(fileDescriptor.getFileDescriptor().valid());
+ try (ByteBufferSession buffer2 =
+ new ByteBufferSession(sharedMem2, /* offset= */ 0, bufSize)) {
+ assertTrue(buffer2.checkSequentialPattern());
+ buffer2.clearMemory();
+ }
+ try (ByteBufferSession buffer1 =
+ new ByteBufferSession(sharedMem1, /* offset= */ 0, bufSize)) {
+ // Clearing the shared RAM via sharedMem2 should be visible to sharedMem1
+ assertTrue(buffer1.hasCleanMemory());
+ }
+ }
+
+ private static final class ByteBufferSession implements AutoCloseable {
+ private final ByteBuffer mBuf;
+ private final int mSize;
+
+ ByteBufferSession(SharedMemory mem, int offset, int length)
+ throws Exception {
+ mBuf = mem.map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, offset, length);
+ mSize = length;
+ }
+
+ @Override
+ public void close() {
+ SharedMemory.unmap(mBuf);
+ }
+
+ public void clearMemory() {
+ mBuf.clear();
+ for (int i = 0; i < mSize; i++) {
+ mBuf.put((byte) 0);
+ }
+ }
+
+ public boolean hasCleanMemory() throws Exception {
+ for (int i = 0; i < mSize; i++) {
+ if (mBuf.get() != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void fillSequentialPattern() {
+ for (int fillPos = 0; fillPos < mSize; fillPos++) {
+ mBuf.put(fillPos, (byte) fillPos);
+ }
+ }
+
+ public boolean checkSequentialPattern() {
+ for (int fillPos = 0; fillPos < mSize; fillPos++) {
+ if (mBuf.get() != (byte) fillPos) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/tests/tests/os/src/android/os/cts/VibrationAttributesTest.java b/tests/tests/os/src/android/os/cts/VibrationAttributesTest.java
index 966780b..9d64927 100644
--- a/tests/tests/os/src/android/os/cts/VibrationAttributesTest.java
+++ b/tests/tests/os/src/android/os/cts/VibrationAttributesTest.java
@@ -21,7 +21,6 @@
import android.media.AudioAttributes;
import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -29,35 +28,73 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.HashMap;
+import java.util.Map;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class VibrationAttributesTest {
- private static final int TEST_USAGE = AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY;
+ private static final Map<Integer, Integer> AUDIO_TO_VIBRATION_USAGE_MAP;
+ static {
+ Map<Integer, Integer> map = new HashMap<>();
+ map.put(AudioAttributes.USAGE_ALARM, VibrationAttributes.USAGE_ALARM);
+ map.put(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
+ VibrationAttributes.USAGE_ACCESSIBILITY);
+ map.put(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION, VibrationAttributes.USAGE_TOUCH);
+ map.put(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
+ VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
+ map.put(AudioAttributes.USAGE_ASSISTANT, VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
+ map.put(AudioAttributes.USAGE_GAME, VibrationAttributes.USAGE_MEDIA);
+ map.put(AudioAttributes.USAGE_MEDIA, VibrationAttributes.USAGE_MEDIA);
+ map.put(AudioAttributes.USAGE_NOTIFICATION, VibrationAttributes.USAGE_NOTIFICATION);
+ map.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
+ VibrationAttributes.USAGE_NOTIFICATION);
+ map.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
+ VibrationAttributes.USAGE_NOTIFICATION);
+ map.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
+ VibrationAttributes.USAGE_NOTIFICATION);
+ map.put(AudioAttributes.USAGE_NOTIFICATION_EVENT, VibrationAttributes.USAGE_NOTIFICATION);
+ map.put(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, VibrationAttributes.USAGE_RINGTONE);
+ map.put(AudioAttributes.USAGE_UNKNOWN, VibrationAttributes.USAGE_UNKNOWN);
+ map.put(AudioAttributes.USAGE_VOICE_COMMUNICATION,
+ VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
+ map.put(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING,
+ VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
+ AUDIO_TO_VIBRATION_USAGE_MAP = map;
+ }
- private static final int TEST_AMPLITUDE = 100;
- private static final long TEST_TIMING_LONG = 5000;
- private static final long TEST_TIMING_SHORT = 4999;
- private static final long[] TEST_TIMINGS_LONG = new long[] { 100, 100, 4800 };
- private static final long[] TEST_TIMINGS_SHORT = new long[] { 100, 100, 4799 };
-
-
- private static final VibrationEffect TEST_ONE_SHOT_LONG =
- VibrationEffect.createOneShot(TEST_TIMING_LONG, TEST_AMPLITUDE);
- private static final VibrationEffect TEST_ONE_SHOT_SHORT =
- VibrationEffect.createOneShot(TEST_TIMING_SHORT, TEST_AMPLITUDE);
- private static final VibrationEffect TEST_WAVEFORM_LONG =
- VibrationEffect.createWaveform(TEST_TIMINGS_LONG, -1);
- private static final VibrationEffect TEST_WAVEFORM_SHORT =
- VibrationEffect.createWaveform(TEST_TIMINGS_SHORT, -1);
- private static final VibrationEffect TEST_PREBAKED =
- VibrationEffect.get(VibrationEffect.EFFECT_CLICK, true);
+ private static final Map<Integer, Integer> VIBRATION_TO_AUDIO_USAGE_MAP;
+ static {
+ Map<Integer, Integer> map = new HashMap<>();
+ map.put(VibrationAttributes.USAGE_ALARM, AudioAttributes.USAGE_ALARM);
+ map.put(VibrationAttributes.USAGE_ACCESSIBILITY,
+ AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY);
+ map.put(VibrationAttributes.USAGE_COMMUNICATION_REQUEST,
+ AudioAttributes.USAGE_VOICE_COMMUNICATION);
+ map.put(VibrationAttributes.USAGE_HARDWARE_FEEDBACK, AudioAttributes.USAGE_UNKNOWN);
+ map.put(VibrationAttributes.USAGE_MEDIA, AudioAttributes.USAGE_MEDIA);
+ map.put(VibrationAttributes.USAGE_NOTIFICATION, AudioAttributes.USAGE_NOTIFICATION);
+ map.put(VibrationAttributes.USAGE_PHYSICAL_EMULATION, AudioAttributes.USAGE_UNKNOWN);
+ map.put(VibrationAttributes.USAGE_RINGTONE, AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
+ map.put(VibrationAttributes.USAGE_TOUCH, AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
+ VIBRATION_TO_AUDIO_USAGE_MAP = map;
+ }
@Test
- public void testCreate() {
- AudioAttributes tmp = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ALARM)
- .build();
- VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
+ public void testCreateFromVibrationAttributes() {
+ VibrationAttributes tmp = VibrationAttributes.createForUsage(
+ VibrationAttributes.USAGE_TOUCH);
+ VibrationAttributes attr = new VibrationAttributes.Builder(tmp).build();
+ assertEquals(attr.getUsage(), VibrationAttributes.USAGE_TOUCH);
+ assertEquals(attr.getUsageClass(), VibrationAttributes.USAGE_CLASS_FEEDBACK);
+ assertEquals(attr.getFlags(), 0);
+ assertEquals(attr.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
+ }
+
+ @Test
+ public void testCreateFromAudioAttributes() {
+ AudioAttributes audioAttributes = createAudioAttributes(AudioAttributes.USAGE_ALARM);
+ VibrationAttributes attr = new VibrationAttributes.Builder(audioAttributes).build();
assertEquals(attr.getUsage(), VibrationAttributes.USAGE_ALARM);
assertEquals(attr.getUsageClass(), VibrationAttributes.USAGE_CLASS_ALARM);
assertEquals(attr.getFlags(), 0);
@@ -65,90 +102,91 @@
}
@Test
+ public void testCreateForUsage() {
+ VibrationAttributes attr = VibrationAttributes.createForUsage(
+ VibrationAttributes.USAGE_RINGTONE);
+ assertEquals(attr.getUsage(), VibrationAttributes.USAGE_RINGTONE);
+ assertEquals(attr.getUsageClass(), VibrationAttributes.USAGE_CLASS_ALARM);
+ assertEquals(attr.getFlags(), 0);
+ assertEquals(attr.getAudioUsage(), AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
+ }
+
+ @Test
public void testGetAudioUsageReturnOriginalUsage() {
- AudioAttributes tmp = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY)
- .build();
- VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
+ VibrationAttributes attr = createForAudioUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION);
assertEquals(attr.getUsage(), VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
- assertEquals(attr.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY);
+ assertEquals(attr.getAudioUsage(), AudioAttributes.USAGE_VOICE_COMMUNICATION);
}
@Test
public void testGetAudioUsageUnknownReturnsBasedOnVibrationUsage() {
- VibrationAttributes attr = new VibrationAttributes.Builder()
- .setUsage(VibrationAttributes.USAGE_NOTIFICATION).build();
+ VibrationAttributes attr = VibrationAttributes.createForUsage(
+ VibrationAttributes.USAGE_NOTIFICATION);
assertEquals(attr.getUsage(), VibrationAttributes.USAGE_NOTIFICATION);
assertEquals(attr.getAudioUsage(), AudioAttributes.USAGE_NOTIFICATION);
}
@Test
+ public void testAudioToVibrationUsageMapping() {
+ for (Map.Entry<Integer, Integer> entry : AUDIO_TO_VIBRATION_USAGE_MAP.entrySet()) {
+ assertEquals(entry.getValue().intValue(), new VibrationAttributes.Builder(
+ createForAudioUsage(entry.getKey())).build().getUsage());
+ }
+ }
+
+ @Test
+ public void testVibrationToAudioUsageMapping() {
+ for (Map.Entry<Integer, Integer> entry : VIBRATION_TO_AUDIO_USAGE_MAP.entrySet()) {
+ assertEquals(entry.getValue().intValue(),
+ new VibrationAttributes.Builder()
+ .setUsage(entry.getKey())
+ .build()
+ .getAudioUsage());
+ }
+ }
+
+ @Test
public void testEquals() {
- AudioAttributes tmp = createAudioAttributes(TEST_USAGE);
- VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
- VibrationAttributes attr2 = new VibrationAttributes.Builder(tmp, null).build();
+ AudioAttributes audioAttributes = createAudioAttributes(
+ AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY);
+ VibrationAttributes attr = new VibrationAttributes.Builder(audioAttributes).build();
+ VibrationAttributes attr2 = new VibrationAttributes.Builder(audioAttributes).build();
assertEquals(attr, attr2);
}
@Test
public void testNotEqualsDifferentAudioUsage() {
- AudioAttributes tmp = createAudioAttributes(
- AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT);
- VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
- AudioAttributes tmp2 = createAudioAttributes(
- AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED);
- VibrationAttributes attr2 = new VibrationAttributes.Builder(tmp2, null).build();
+ VibrationAttributes attr = createForAudioUsage(AudioAttributes.USAGE_NOTIFICATION);
+ VibrationAttributes attr2 = createForAudioUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT);
assertEquals(attr.getUsage(), attr2.getUsage());
assertNotEquals(attr, attr2);
}
@Test
public void testNotEqualsDifferentVibrationUsage() {
- VibrationAttributes attr = new VibrationAttributes.Builder()
- .setUsage(VibrationAttributes.USAGE_TOUCH)
- .build();
- VibrationAttributes attr2 = new VibrationAttributes.Builder()
- .setUsage(VibrationAttributes.USAGE_NOTIFICATION)
- .build();
+ VibrationAttributes attr = VibrationAttributes.createForUsage(
+ VibrationAttributes.USAGE_TOUCH);
+ VibrationAttributes attr2 = VibrationAttributes.createForUsage(
+ VibrationAttributes.USAGE_NOTIFICATION);
assertNotEquals(attr, attr2);
}
@Test
public void testNotEqualsDifferentFlags() {
- AudioAttributes tmp = createAudioAttributes(TEST_USAGE);
- VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
- VibrationAttributes attr2 = new VibrationAttributes.Builder(tmp, null).setFlags(1, 1)
+ AudioAttributes audioAttributes = createAudioAttributes(
+ AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
+ VibrationAttributes attr = new VibrationAttributes.Builder(audioAttributes).build();
+ VibrationAttributes attr2 = new VibrationAttributes.Builder(audioAttributes).setFlags(1, 1)
.build();
assertNotEquals(attr, attr2);
}
- @Test
- public void testHeuristics() {
- AudioAttributes tmp = createAudioAttributes(AudioAttributes.USAGE_UNKNOWN);
- VibrationAttributes oneShotLong =
- new VibrationAttributes.Builder(tmp, TEST_ONE_SHOT_LONG).build();
- VibrationAttributes oneShotShort =
- new VibrationAttributes.Builder(tmp, TEST_ONE_SHOT_SHORT).build();
- VibrationAttributes waveformLong =
- new VibrationAttributes.Builder(tmp, TEST_WAVEFORM_LONG).build();
- VibrationAttributes waveformShort =
- new VibrationAttributes.Builder(tmp, TEST_WAVEFORM_SHORT).build();
- VibrationAttributes prebaked =
- new VibrationAttributes.Builder(tmp, TEST_PREBAKED).build();
- assertEquals(oneShotShort.getUsage(), VibrationAttributes.USAGE_TOUCH);
- assertEquals(oneShotShort.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
- assertEquals(waveformShort.getUsage(), VibrationAttributes.USAGE_TOUCH);
- assertEquals(waveformShort.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
- assertEquals(oneShotLong.getUsage(), VibrationAttributes.USAGE_UNKNOWN);
- assertEquals(oneShotLong.getAudioUsage(), AudioAttributes.USAGE_UNKNOWN);
- assertEquals(waveformLong.getUsage(), VibrationAttributes.USAGE_UNKNOWN);
- assertEquals(waveformLong.getAudioUsage(), AudioAttributes.USAGE_UNKNOWN);
- assertEquals(prebaked.getUsage(), VibrationAttributes.USAGE_TOUCH);
- assertEquals(prebaked.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
+ private static AudioAttributes createAudioAttributes(int audioUsage) {
+ return new AudioAttributes.Builder().setUsage(audioUsage).build();
}
- private static AudioAttributes createAudioAttributes(int usage) {
- return new AudioAttributes.Builder().setUsage(usage).build();
+ private static VibrationAttributes createForAudioUsage(int audioUsage) {
+ return new VibrationAttributes.Builder(createAudioAttributes(audioUsage)).build();
}
}
diff --git a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
index 0b0f5e4..e34ae5d 100644
--- a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
+++ b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
@@ -18,7 +18,6 @@
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.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -63,9 +62,9 @@
private static final VibrationEffect TEST_WAVEFORM_BUILT =
VibrationEffect.startWaveform()
.addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
- .addStep(/* amplitude= */ 0.8f, /* frequency= */ -1f, /* duration= */ 20)
+ .addStep(/* amplitude= */ 0.8f, /* frequency= */ 100f, /* duration= */ 20)
.addRamp(/* amplitude= */ 1f, /* duration= */ 100)
- .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 1f, /* duration= */ 200)
+ .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 200f, /* duration= */ 200)
.build();
private static final VibrationEffect TEST_PREBAKED =
VibrationEffect.get(VibrationEffect.EFFECT_CLICK, true);
@@ -547,9 +546,9 @@
VibrationEffect effect = VibrationEffect.startWaveform()
.addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
- .addStep(/* amplitude= */ 0.8f, /* frequency= */ -1f, /* duration= */ 20)
+ .addStep(/* amplitude= */ 0.8f, /* frequency= */ 100f, /* duration= */ 20)
.addRamp(/* amplitude= */ 1f, /* duration= */ 100)
- .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 1f, /* duration= */ 200)
+ .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 200f, /* duration= */ 200)
.build();
assertArrayEquals(new long[]{10, 20, 100, 200}, getTimings(effect));
@@ -559,24 +558,24 @@
assertStepSegment(effect, 1);
assertAmplitude(0.8f, effect, 1);
- assertFrequency(-1f, effect, 1);
+ assertFrequency(100f, effect, 1);
assertRampSegment(effect, 2);
assertAmplitude(1f, effect, 2);
- assertFrequency(-1f, effect, 2);
+ assertFrequency(100f, effect, 2);
assertRampSegment(effect, 3);
assertAmplitude(0.2f, effect, 3);
- assertFrequency(1f, effect, 3);
+ assertFrequency(200f, effect, 3);
}
@Test
public void testStartWaveformEquals() {
VibrationEffect other = VibrationEffect.startWaveform()
.addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
- .addStep(/* amplitude= */ 0.8f, /* frequency= */ -1f, /* duration= */ 20)
+ .addStep(/* amplitude= */ 0.8f, /* frequency= */ 100f, /* duration= */ 20)
.addRamp(/* amplitude= */ 1f, /* duration= */ 100)
- .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 1f, /* duration= */ 200)
+ .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 200f, /* duration= */ 200)
.build();
assertEquals(TEST_WAVEFORM_BUILT, other);
assertEquals(TEST_WAVEFORM_BUILT.hashCode(), other.hashCode());
@@ -647,10 +646,10 @@
@Test
public void testStartWaveformNotEqualsDifferentFrequency() {
VibrationEffect first = VibrationEffect.startWaveform()
- .addStep(/* amplitude= */ 0.5f, /* frequency= */ 0.5f, /* duration= */ 10)
+ .addStep(/* amplitude= */ 0.5f, /* frequency= */ 1f, /* duration= */ 10)
.build();
VibrationEffect second = VibrationEffect.startWaveform()
- .addStep(/* amplitude= */ 0.5f, /* frequency= */ -1f, /* duration= */ 10)
+ .addStep(/* amplitude= */ 0.5f, /* frequency= */ 50f, /* duration= */ 10)
.build();
assertNotEquals(first, second);
}
@@ -735,11 +734,12 @@
assertTrue(index < composed.getSegments().size());
VibrationEffectSegment segment = composed.getSegments().get(index);
if (segment instanceof StepSegment) {
- assertEquals(expected, ((StepSegment) composed.getSegments().get(index)).getFrequency(),
+ assertEquals(expected,
+ ((StepSegment) composed.getSegments().get(index)).getFrequencyHz(),
TEST_TOLERANCE);
} else if (segment instanceof RampSegment) {
assertEquals(expected,
- ((RampSegment) composed.getSegments().get(index)).getEndFrequency(),
+ ((RampSegment) composed.getSegments().get(index)).getEndFrequencyHz(),
TEST_TOLERANCE);
} else {
fail("Expected a step or ramp segment at index " + index + " of " + effect);
diff --git a/tests/tests/os/src/android/os/cts/VibratorManagerTest.java b/tests/tests/os/src/android/os/cts/VibratorManagerTest.java
index 398938bb..2ddecab 100644
--- a/tests/tests/os/src/android/os/cts/VibratorManagerTest.java
+++ b/tests/tests/os/src/android/os/cts/VibratorManagerTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
@@ -68,15 +69,20 @@
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
- private static final long CALLBACK_TIMEOUT_MILLIS = 5000;
+ private static final long CALLBACK_TIMEOUT_MILLIS = 5_000;
private static final VibrationAttributes VIBRATION_ATTRIBUTES =
new VibrationAttributes.Builder()
.setUsage(VibrationAttributes.USAGE_TOUCH)
.build();
- private VibratorManager mVibratorManager;
+ /**
+ * These listeners are used for test helper methods like asserting it starts/stops vibrating.
+ * It's not strongly required that the interactions with these mocks are validated by all tests.
+ */
private final SparseArray<OnVibratorStateChangedListener> mStateListeners = new SparseArray<>();
+ private VibratorManager mVibratorManager;
+
@Before
public void setUp() {
mVibratorManager =
@@ -87,13 +93,34 @@
OnVibratorStateChangedListener listener = mock(OnVibratorStateChangedListener.class);
mVibratorManager.getVibrator(vibratorId).addVibratorStateListener(listener);
mStateListeners.put(vibratorId, listener);
- reset(listener);
+ // Adding a listener to the Vibrator should trigger the callback once with the current
+ // vibrator state, so reset mocks to clear it for tests.
+ assertVibratorState(false);
+ clearInvocations(listener);
}
}
@After
public void cleanUp() {
+ // Clearing invocations so we can use these listeners to wait for the vibrator to
+ // asynchronously cancel the ongoing vibration, if any was left pending by a test.
+ for (int i = 0; i < mStateListeners.size(); i++) {
+ clearInvocations(mStateListeners.valueAt(i));
+ }
mVibratorManager.cancel();
+
+ for (int i = 0; i < mStateListeners.size(); i++) {
+ int vibratorId = mStateListeners.keyAt(i);
+
+ // Wait for cancel to take effect, if device is still vibrating.
+ if (mVibratorManager.getVibrator(vibratorId).isVibrating()) {
+ assertStopsVibrating(vibratorId);
+ }
+
+ // Remove all listeners added by the tests.
+ mVibratorManager.getVibrator(vibratorId).removeVibratorStateListener(
+ mStateListeners.valueAt(i));
+ }
}
@Test
@@ -108,20 +135,26 @@
@LargeTest
@Test
- public void testVibrateOneShot() {
+ public void testVibrateOneShotStartsAndFinishesVibration() {
VibrationEffect oneShot =
VibrationEffect.createOneShot(300, VibrationEffect.DEFAULT_AMPLITUDE);
mVibratorManager.vibrate(CombinedVibration.createParallel(oneShot));
assertStartsThenStopsVibrating(300);
+ }
- oneShot = VibrationEffect.createOneShot(500, 255 /* Max amplitude */);
+ @Test
+ public void testVibrateOneShotMaxAmplitude() {
+ VibrationEffect oneShot = VibrationEffect.createOneShot(500, 255 /* Max amplitude */);
mVibratorManager.vibrate(CombinedVibration.createParallel(oneShot));
assertStartsVibrating();
mVibratorManager.cancel();
assertStopsVibrating();
+ }
- oneShot = VibrationEffect.createOneShot(100, 1 /* Min amplitude */);
+ @Test
+ public void testVibrateOneShotMinAmplitude() {
+ VibrationEffect oneShot = VibrationEffect.createOneShot(100, 1 /* Min amplitude */);
mVibratorManager.vibrate(CombinedVibration.createParallel(oneShot),
VIBRATION_ATTRIBUTES);
assertStartsVibrating();
@@ -129,17 +162,29 @@
@LargeTest
@Test
- public void testVibrateWaveform() {
+ public void testVibrateWaveformStartsAndFinishesVibration() {
final long[] timings = new long[]{100, 200, 300, 400, 500};
final int[] amplitudes = new int[]{64, 128, 255, 128, 64};
VibrationEffect waveform = VibrationEffect.createWaveform(timings, amplitudes, -1);
mVibratorManager.vibrate(CombinedVibration.createParallel(waveform));
assertStartsThenStopsVibrating(1500);
+ }
- waveform = VibrationEffect.createWaveform(timings, amplitudes, 0);
+ @LargeTest
+ @Test
+ public void testVibrateWaveformRepeats() {
+ final long[] timings = new long[]{100, 200, 300, 400, 500};
+ final int[] amplitudes = new int[]{64, 128, 255, 128, 64};
+ VibrationEffect waveform = VibrationEffect.createWaveform(timings, amplitudes, 0);
mVibratorManager.vibrate(CombinedVibration.createParallel(waveform));
assertStartsVibrating();
+ SystemClock.sleep(2000);
+ int[] vibratorIds = mVibratorManager.getVibratorIds();
+ for (int vibratorId : vibratorIds) {
+ assertTrue(mVibratorManager.getVibrator(vibratorId).isVibrating());
+ }
+
mVibratorManager.cancel();
assertStopsVibrating();
}
@@ -227,8 +272,7 @@
private void assertStartsThenStopsVibrating(long duration) {
for (int i = 0; i < mStateListeners.size(); i++) {
- verify(mStateListeners.valueAt(i), timeout(CALLBACK_TIMEOUT_MILLIS).atLeastOnce())
- .onVibratorStateChanged(true);
+ assertVibratorState(mStateListeners.keyAt(i), true);
}
SystemClock.sleep(duration);
assertVibratorState(false);
@@ -260,6 +304,5 @@
OnVibratorStateChangedListener listener = mStateListeners.get(vibratorId);
verify(listener, timeout(CALLBACK_TIMEOUT_MILLIS).atLeastOnce())
.onVibratorStateChanged(eq(expected));
- reset(listener);
}
}
diff --git a/tests/tests/os/src/android/os/cts/VibratorTest.java b/tests/tests/os/src/android/os/cts/VibratorTest.java
index cf2460c..7085ba5 100644
--- a/tests/tests/os/src/android/os/cts/VibratorTest.java
+++ b/tests/tests/os/src/android/os/cts/VibratorTest.java
@@ -18,18 +18,19 @@
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 static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import android.media.AudioAttributes;
import android.os.SystemClock;
+import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.Vibrator.OnVibratorStateChangedListener;
@@ -50,6 +51,8 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.Executors;
@RunWith(AndroidJUnit4.class)
@@ -72,6 +75,10 @@
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
+ private static final VibrationAttributes VIBRATION_ATTRIBUTES =
+ new VibrationAttributes.Builder()
+ .setUsage(VibrationAttributes.USAGE_TOUCH)
+ .build();
private static final long CALLBACK_TIMEOUT_MILLIS = 5000;
private static final int[] PREDEFINED_EFFECTS = new int[]{
VibrationEffect.EFFECT_CLICK,
@@ -92,23 +99,74 @@
VibrationEffect.Composition.PRIMITIVE_SPIN,
VibrationEffect.Composition.PRIMITIVE_THUD,
};
+ private static final int[] VIBRATION_USAGES = new int[] {
+ VibrationAttributes.USAGE_UNKNOWN,
+ VibrationAttributes.USAGE_ACCESSIBILITY,
+ VibrationAttributes.USAGE_ALARM,
+ VibrationAttributes.USAGE_COMMUNICATION_REQUEST,
+ VibrationAttributes.USAGE_HARDWARE_FEEDBACK,
+ VibrationAttributes.USAGE_MEDIA,
+ VibrationAttributes.USAGE_NOTIFICATION,
+ VibrationAttributes.USAGE_PHYSICAL_EMULATION,
+ VibrationAttributes.USAGE_RINGTONE,
+ VibrationAttributes.USAGE_TOUCH,
+ };
- private Vibrator mVibrator;
+
+ /**
+ * This listener is used for test helper methods like asserting it starts/stops vibrating.
+ * It's not strongly required that the interactions with this mock are validated by all tests.
+ */
@Mock
private OnVibratorStateChangedListener mStateListener;
+ private Vibrator mVibrator;
+ /** Keep track of any listener created to be added to the vibrator, for cleanup purposes. */
+ private List<OnVibratorStateChangedListener> mStateListenersCreated = new ArrayList<>();
+
@Before
public void setUp() {
mVibrator = InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
Vibrator.class);
mVibrator.addVibratorStateListener(mStateListener);
- reset(mStateListener);
+ // Adding a listener to the Vibrator should trigger the callback once with the current
+ // vibrator state, so reset mocks to clear it for tests.
+ assertVibratorState(false);
+ clearInvocations(mStateListener);
}
@After
public void cleanUp() {
+ // Clearing invocations so we can use this listener to wait for the vibrator to
+ // asynchronously cancel the ongoing vibration, if any was left pending by a test.
+ clearInvocations(mStateListener);
mVibrator.cancel();
+
+ // Wait for cancel to take effect, if device is still vibrating.
+ if (mVibrator.isVibrating()) {
+ assertStopsVibrating();
+ }
+
+ // Remove all listeners added by the tests.
+ mVibrator.removeVibratorStateListener(mStateListener);
+ for (OnVibratorStateChangedListener listener : mStateListenersCreated) {
+ mVibrator.removeVibratorStateListener(listener);
+ }
+ }
+
+ @Test
+ public void getDefaultVibrationIntensity_returnsValidIntensityForAllUsages() {
+ for (int usage : VIBRATION_USAGES) {
+ int intensity = mVibrator.getDefaultVibrationIntensity(usage);
+ assertTrue("Error for usage " + usage + " with default intensity " + intensity,
+ (intensity >= Vibrator.VIBRATION_INTENSITY_OFF)
+ && (intensity <= Vibrator.VIBRATION_INTENSITY_HIGH));
+ }
+
+ assertEquals("Invalid usage expected to have same default as USAGE_UNKNOWN",
+ mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_UNKNOWN),
+ mVibrator.getDefaultVibrationIntensity(-1));
}
@Test
@@ -156,34 +214,46 @@
@LargeTest
@Test
- public void testVibrateOneShot() {
+ public void testVibrateOneShotStartsAndFinishesVibration() {
VibrationEffect oneShot =
VibrationEffect.createOneShot(300, VibrationEffect.DEFAULT_AMPLITUDE);
mVibrator.vibrate(oneShot);
assertStartsThenStopsVibrating(300);
+ }
- oneShot = VibrationEffect.createOneShot(10_000, 255 /* Max amplitude */);
+ @Test
+ public void testVibrateOneShotMaxAmplitude() {
+ VibrationEffect oneShot = VibrationEffect.createOneShot(10_000, 255 /* Max amplitude */);
mVibrator.vibrate(oneShot);
assertStartsVibrating();
mVibrator.cancel();
assertStopsVibrating();
+ }
- oneShot = VibrationEffect.createOneShot(300, 1 /* Min amplitude */);
+ @Test
+ public void testVibrateOneShotMinAmplitude() {
+ VibrationEffect oneShot = VibrationEffect.createOneShot(300, 1 /* Min amplitude */);
mVibrator.vibrate(oneShot, AUDIO_ATTRIBUTES);
assertStartsVibrating();
}
@LargeTest
@Test
- public void testVibrateWaveform() {
- final long[] timings = new long[] {100, 200, 300, 400, 500};
- final int[] amplitudes = new int[] {64, 128, 255, 128, 64};
+ public void testVibrateWaveformStartsAndFinishesVibration() {
+ final long[] timings = new long[]{100, 200, 300, 400, 500};
+ final int[] amplitudes = new int[]{64, 128, 255, 128, 64};
VibrationEffect waveform = VibrationEffect.createWaveform(timings, amplitudes, -1);
mVibrator.vibrate(waveform);
assertStartsThenStopsVibrating(1500);
+ }
- waveform = VibrationEffect.createWaveform(timings, amplitudes, 0);
+ @LargeTest
+ @Test
+ public void testVibrateWaveformRepeats() {
+ final long[] timings = new long[] {100, 200, 300, 400, 500};
+ final int[] amplitudes = new int[] {64, 128, 255, 128, 64};
+ VibrationEffect waveform = VibrationEffect.createWaveform(timings, amplitudes, 0);
mVibrator.vibrate(waveform, AUDIO_ATTRIBUTES);
assertStartsVibrating();
@@ -221,6 +291,12 @@
}
@Test
+ public void testVibrateWithAttributes() {
+ mVibrator.vibrate(VibrationEffect.createOneShot(10, 10), VIBRATION_ATTRIBUTES);
+ assertStartsVibrating();
+ }
+
+ @Test
public void testGetId() {
// The system vibrator should not be mapped to any physical vibrator and use a default id.
assertEquals(-1, mVibrator.getId());
@@ -326,8 +402,8 @@
return;
}
- OnVibratorStateChangedListener listener1 = mock(OnVibratorStateChangedListener.class);
- OnVibratorStateChangedListener listener2 = mock(OnVibratorStateChangedListener.class);
+ OnVibratorStateChangedListener listener1 = newMockStateListener();
+ OnVibratorStateChangedListener listener2 = newMockStateListener();
// Add listener1 on executor
mVibrator.addVibratorStateListener(Executors.newSingleThreadExecutor(), listener1);
// Add listener2 on main thread.
@@ -335,33 +411,54 @@
verify(listener1, timeout(CALLBACK_TIMEOUT_MILLIS).times(1)).onVibratorStateChanged(false);
verify(listener2, timeout(CALLBACK_TIMEOUT_MILLIS).times(1)).onVibratorStateChanged(false);
- mVibrator.vibrate(1000);
+ mVibrator.vibrate(10);
+ assertStartsVibrating();
verify(listener1, timeout(CALLBACK_TIMEOUT_MILLIS).times(1)).onVibratorStateChanged(true);
verify(listener2, timeout(CALLBACK_TIMEOUT_MILLIS).times(1)).onVibratorStateChanged(true);
+ // The state changes back to false after vibration ends.
+ verify(listener1, timeout(CALLBACK_TIMEOUT_MILLIS).times(2)).onVibratorStateChanged(false);
+ verify(listener2, timeout(CALLBACK_TIMEOUT_MILLIS).times(2)).onVibratorStateChanged(false);
+ }
- mVibrator.cancel();
- assertStopsVibrating();
+ @LargeTest
+ @Test
+ public void testVibratorStateCallbackRemoval() {
+ if (!mVibrator.hasVibrator()) {
+ return;
+ }
+
+ OnVibratorStateChangedListener listener1 = newMockStateListener();
+ OnVibratorStateChangedListener listener2 = newMockStateListener();
+ // Add listener1 on executor
+ mVibrator.addVibratorStateListener(Executors.newSingleThreadExecutor(), listener1);
+ // Add listener2 on main thread.
+ mVibrator.addVibratorStateListener(listener2);
+ verify(listener1, timeout(CALLBACK_TIMEOUT_MILLIS).times(1)).onVibratorStateChanged(false);
+ verify(listener2, timeout(CALLBACK_TIMEOUT_MILLIS).times(1)).onVibratorStateChanged(false);
// Remove listener1 & listener2
mVibrator.removeVibratorStateListener(listener1);
mVibrator.removeVibratorStateListener(listener2);
- reset(listener1);
- reset(listener2);
mVibrator.vibrate(1000);
assertStartsVibrating();
- verify(listener1, timeout(CALLBACK_TIMEOUT_MILLIS).times(0))
- .onVibratorStateChanged(anyBoolean());
- verify(listener2, timeout(CALLBACK_TIMEOUT_MILLIS).times(0))
- .onVibratorStateChanged(anyBoolean());
+ // Wait the timeout to assert there was no more interactions with the removed listeners.
+ verify(listener1, after(CALLBACK_TIMEOUT_MILLIS).never()).onVibratorStateChanged(true);
+ // Previous call was blocking, so no need to wait for a timeout here as well.
+ verify(listener2, never()).onVibratorStateChanged(true);
+ }
+
+ private OnVibratorStateChangedListener newMockStateListener() {
+ OnVibratorStateChangedListener listener = mock(OnVibratorStateChangedListener.class);
+ mStateListenersCreated.add(listener);
+ return listener;
}
private void assertStartsThenStopsVibrating(long duration) {
if (mVibrator.hasVibrator()) {
- verify(mStateListener, timeout(CALLBACK_TIMEOUT_MILLIS).atLeastOnce())
- .onVibratorStateChanged(true);
+ assertVibratorState(true);
SystemClock.sleep(duration);
assertVibratorState(false);
}
@@ -379,7 +476,6 @@
if (mVibrator.hasVibrator()) {
verify(mStateListener, timeout(CALLBACK_TIMEOUT_MILLIS).atLeastOnce())
.onVibratorStateChanged(eq(expected));
- reset(mStateListener);
}
}
}
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 7150d51..96309b8 100644
--- a/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
+++ b/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
import android.app.PendingIntent;
@@ -31,6 +33,7 @@
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.ProxyFileDescriptorCallback;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.cts.R;
import android.os.storage.OnObbStateChangeListener;
@@ -98,6 +101,11 @@
}
}
+ // b/215354735 - certain tests fail with fuse-bpf, so temporarily disable
+ private boolean isFuseBpf() throws Exception {
+ return SystemProperties.getBoolean("persist.sys.fuse.bpf.enable", false);
+ }
+
private void doMountAndUnmountObbNormal(File outFile) throws IOException {
final String canonPath = mountObb(R.raw.test1_new, outFile, OnObbStateChangeListener.MOUNTED);
@@ -221,6 +229,7 @@
final boolean removable = volume.isRemovable();
final boolean emulated = volume.isEmulated();
if (emulated) {
+ assertFalse("Should not be externally managed", volume.isExternallyManaged());
assertFalse("Should not be removable", removable);
assertNull("Should not have fsUuid", fsUuid);
assertEquals("Should have uuid_default", StorageManager.UUID_DEFAULT, uuid);
@@ -327,6 +336,7 @@
@AppModeFull(reason = "Instant apps cannot access external storage")
public void testCallback() throws Exception {
+ if (isFuseBpf()) return; // b/215354735
final CountDownLatch mounted = new CountDownLatch(1);
final CountDownLatch unmounted = new CountDownLatch(1);
final StorageVolumeCallback callback = new StorageVolumeCallback() {
@@ -1010,6 +1020,32 @@
}
}
+ public void testComputeStorageCacheBytesHint() throws Exception {
+ File mockFile = mock(File.class);
+
+ when(mockFile.getUsableSpace()).thenReturn(10000L);
+ when(mockFile.getTotalSpace()).thenReturn(15000L);
+ final long resultHigh = mStorageManager.computeStorageCacheBytes(mockFile);
+ assertTrue("" + resultHigh + " expected to be greater than equal to 0", resultHigh >= 0L);
+ assertTrue("" + resultHigh + " expected to be less than equal to total space",
+ resultHigh <= mockFile.getTotalSpace());
+
+ when(mockFile.getUsableSpace()).thenReturn(10000L);
+ when(mockFile.getTotalSpace()).thenReturn(250000L);
+ final long resultLow = mStorageManager.computeStorageCacheBytes(mockFile);
+ assertTrue("" + resultLow + " expected to be greater than equal to 0", resultLow >= 0L);
+ assertTrue("" + resultLow + " expected to be less than equal to total space",
+ resultLow <= mockFile.getTotalSpace());
+
+ when(mockFile.getUsableSpace()).thenReturn(10000L);
+ when(mockFile.getTotalSpace()).thenReturn(100000L);
+ final long resultModerate = mStorageManager.computeStorageCacheBytes(mockFile);
+ assertTrue("" + resultModerate + " expected to be greater than equal to 0",
+ resultModerate >= 0L);
+ assertTrue("" + resultModerate + " expected to be less than equal to total space",
+ resultModerate <= mockFile.getTotalSpace());
+ }
+
public static byte[] readFully(InputStream in) throws IOException {
// Shamelessly borrowed from libcore.io.Streams
try {
diff --git a/tests/tests/packageinstaller/TEST_MAPPING b/tests/tests/packageinstaller/TEST_MAPPING
index 0d326d5..a17298a 100644
--- a/tests/tests/packageinstaller/TEST_MAPPING
+++ b/tests/tests/packageinstaller/TEST_MAPPING
@@ -1,4 +1,30 @@
{
+ "presubmit": [
+ {
+ "name": "CtsAdminPackageInstallerTestCases"
+ },
+ {
+ "name": "CtsPackageInstallTestCases"
+ },
+ {
+ "name": "CtsPackageInstallAppOpDefaultTestCases"
+ },
+ {
+ "name": "CtsPackageInstallAppOpDeniedTestCases"
+ },
+ {
+ "name": "CtsNoPermissionTestCases"
+ },
+ {
+ "name": "CtsNoPermissionTestCases25"
+ },
+ {
+ "name": "CtsPackageInstallerTapjackingTestCases"
+ },
+ {
+ "name": "CtsPackageUninstallTestCases"
+ }
+ ],
"imports": [
{
"path": "cts/tests/tests/packageinstaller/atomicinstall"
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
index 6fc29db..5b93092 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
@@ -50,7 +50,6 @@
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit
-const val TEST_APK_NAME = "CtsEmptyTestApp.apk"
const val TEST_APK_PACKAGE_NAME = "android.packageinstaller.emptytestapp.cts"
const val TEST_APK_EXTERNAL_LOCATION = "/data/local/tmp/cts/packageinstaller"
const val INSTALL_ACTION_CB = "PackageInstallerTestBase.install_cb"
@@ -66,6 +65,10 @@
const val INSTALL_INSTANT_APP = 0x00000800
open class PackageInstallerTestBase {
+ companion object {
+ const val TEST_APK_NAME = "CtsEmptyTestApp.apk"
+ }
+
@get:Rule
val installDialogStarter = ActivityTestRule(FutureResultActivity::class.java)
@@ -138,6 +141,13 @@
}
protected fun startInstallationViaSession(installFlags: Int): CompletableFuture<Int> {
+ return startInstallationViaSession(installFlags, TEST_APK_NAME)
+ }
+
+ private fun createSession(
+ installFlags: Int,
+ isMultiPackage: Boolean
+ ): Pair<Int, PackageInstaller.Session> {
val pi = pm.packageInstaller
// Create session
@@ -146,20 +156,31 @@
if (installFlags and INSTALL_INSTANT_APP != 0) {
sessionParam.setInstallAsInstantApp(true)
}
+ if (isMultiPackage) {
+ sessionParam.setMultiPackage()
+ }
val sessionId = pi.createSession(sessionParam)
val session = pi.openSession(sessionId)!!
+ return Pair(sessionId, session)
+ }
+
+ private fun writeSession(session: PackageInstaller.Session, apkName: String) {
+ val apkFile = File(context.filesDir, apkName)
// Write data to session
apkFile.inputStream().use { fileOnDisk ->
- session.openWrite(TEST_APK_NAME, 0, -1).use { sessionFile ->
+ session.openWrite(apkName, 0, -1).use { sessionFile ->
fileOnDisk.copyTo(sessionFile)
}
}
+ }
+ private fun commitSession(session: PackageInstaller.Session): CompletableFuture<Int> {
// Commit session
val dialog = FutureResultActivity.doAndAwaitStart {
- val pendingIntent = PendingIntent.getBroadcast(context, 0, Intent(INSTALL_ACTION_CB),
+ val pendingIntent = PendingIntent.getBroadcast(
+ context, 0, Intent(INSTALL_ACTION_CB),
FLAG_UPDATE_CURRENT or FLAG_MUTABLE)
session.commit(pendingIntent.intentSender)
}
@@ -170,6 +191,28 @@
return dialog
}
+ protected fun startInstallationViaSession(
+ installFlags: Int,
+ apkName: String
+ ): CompletableFuture<Int> {
+ val (sessionId, session) = createSession(installFlags, false)
+ writeSession(session, apkName)
+ return commitSession(session)
+ }
+
+ protected fun startInstallationViaMultiPackageSession(
+ installFlags: Int,
+ vararg apkNames: String
+ ): CompletableFuture<Int> {
+ val (sessionId, session) = createSession(installFlags, true)
+ for (apkName in apkNames) {
+ val (childSessionId, childSession) = createSession(installFlags, false)
+ writeSession(childSession, apkName)
+ session.addChildSessionId(childSessionId)
+ }
+ return commitSession(session)
+ }
+
/**
* Start an installation via a session
*/
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
index e81f1d1..957b66f 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
@@ -63,6 +63,27 @@
}
/**
+ * Check that we can install an app via a package-installer session
+ */
+ @Test
+ fun confirmMultiPackageInstallation() {
+ val installation = startInstallationViaMultiPackageSession(
+ installFlags = 0,
+ PackageInstallerTestBase.TEST_APK_NAME
+ )
+ clickInstallerUIButton(INSTALL_BUTTON_ID)
+
+ // Install should have succeeded
+ assertEquals(STATUS_SUCCESS, getInstallSessionResult())
+ assertInstalled()
+
+ // Even when the install succeeds the install confirm dialog returns 'canceled'
+ assertEquals(RESULT_CANCELED, installation.get(TIMEOUT, TimeUnit.MILLISECONDS))
+
+ assertTrue(AppOpsUtils.allowedOperationLogged(context.packageName, APP_OP_STR))
+ }
+
+ /**
* Check that we can set an app category for an app we installed
*/
@Test
diff --git a/tests/tests/packageinstaller/nopermission/AndroidTest.xml b/tests/tests/packageinstaller/nopermission/AndroidTest.xml
index 08ae8cc..49fcf89 100644
--- a/tests/tests/packageinstaller/nopermission/AndroidTest.xml
+++ b/tests/tests/packageinstaller/nopermission/AndroidTest.xml
@@ -20,6 +20,7 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/cts/nopermission" />
diff --git a/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt b/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
index e60e53a..43a3844 100644
--- a/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
+++ b/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
@@ -113,7 +113,8 @@
@Before
fun registerInstallResultReceiver() {
- context.registerReceiver(receiver, IntentFilter(ACTION))
+ context.registerReceiver(receiver, IntentFilter(ACTION),
+ Context.RECEIVER_EXPORTED_UNAUDITED)
}
@Before
diff --git a/tests/tests/packageinstaller/nopermission25/AndroidTest.xml b/tests/tests/packageinstaller/nopermission25/AndroidTest.xml
index 3c69674..c84a81f 100644
--- a/tests/tests/packageinstaller/nopermission25/AndroidTest.xml
+++ b/tests/tests/packageinstaller/nopermission25/AndroidTest.xml
@@ -21,6 +21,7 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/cts/nopermission" />
diff --git a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
index 6aaeade..e501c90 100644
--- a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
+++ b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
@@ -30,7 +30,6 @@
// tag this module as a cts test artifact
test_suites: [
- "arcts",
"cts",
"general-tests",
"sts",
diff --git a/tests/tests/permission/AndroidTest.xml b/tests/tests/permission/AndroidTest.xml
index 8e4ff03..424da2e 100644
--- a/tests/tests/permission/AndroidTest.xml
+++ b/tests/tests/permission/AndroidTest.xml
@@ -20,6 +20,7 @@
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
<option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
@@ -77,6 +78,7 @@
<option name="push" value="CtsInstallPermissionUserApp.apk->/data/local/tmp/cts/permissions/CtsInstallPermissionUserApp.apk" />
<option name="push" value="CtsInstallPermissionEscalatorApp.apk->/data/local/tmp/cts/permissions/CtsInstallPermissionEscalatorApp.apk" />
<option name="push" value="CtsAppThatRequestsOneTimePermission.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsOneTimePermission.apk" />
+ <option name="push" value="CtsAppToTestSelfRevokePermission.apk->/data/local/tmp/cts/permissions/CtsAppToTestSelfRevokePermission.apk" />
<option name="push" value="AppThatDefinesUndefinedPermissionGroupElement.apk->/data/local/tmp/cts/permissions/AppThatDefinesUndefinedPermissionGroupElement.apk" />
<option name="push" value="CtsAppThatDefinesPermissionA.apk->/data/local/tmp/cts/permissions/CtsAppThatDefinesPermissionA.apk" />
<option name="push" value="CtsAppThatAlsoDefinesPermissionA.apk->/data/local/tmp/cts/permissions/CtsAppThatAlsoDefinesPermissionA.apk" />
diff --git a/tests/tests/permission/AppToTestSelfRevokePermission/Android.bp b/tests/tests/permission/AppToTestSelfRevokePermission/Android.bp
new file mode 100644
index 0000000..88d1c60
--- /dev/null
+++ b/tests/tests/permission/AppToTestSelfRevokePermission/Android.bp
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAppToTestSelfRevokePermission",
+ defaults: [
+ "cts_defaults",
+ "mts-target-sdk-version-current",
+ ],
+ min_sdk_version: "30",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "mts",
+ "general-tests",
+ ],
+ srcs: ["src/**/*.java"],
+}
diff --git a/tests/tests/permission/AppToTestSelfRevokePermission/AndroidManifest.xml b/tests/tests/permission/AppToTestSelfRevokePermission/AndroidManifest.xml
new file mode 100644
index 0000000..2e2a759
--- /dev/null
+++ b/tests/tests/permission/AppToTestSelfRevokePermission/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.permission.cts.apptotestselfrevokepermission">
+
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
+
+ <application>
+ <activity android:name=".RevokePermission" android:exported="true"
+ android:visibleToInstantApps="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/permission/AppToTestSelfRevokePermission/src/android/permission/cts/apptotestselfrevokepermission/RevokePermission.java b/tests/tests/permission/AppToTestSelfRevokePermission/src/android/permission/cts/apptotestselfrevokepermission/RevokePermission.java
new file mode 100644
index 0000000..bc40924
--- /dev/null
+++ b/tests/tests/permission/AppToTestSelfRevokePermission/src/android/permission/cts/apptotestselfrevokepermission/RevokePermission.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts.apptotestselfrevokepermission;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+import java.util.Arrays;
+
+public class RevokePermission extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+ String[] permissions = intent.getStringArrayExtra("permissions");
+ if (permissions.length == 1) {
+ getApplicationContext().selfRevokePermission(permissions[0]);
+ } else {
+ getApplicationContext().selfRevokePermissions(Arrays.asList(permissions));
+ }
+ }
+}
diff --git a/tests/tests/permission/nativeTests/AndroidTest.xml b/tests/tests/permission/nativeTests/AndroidTest.xml
index 23064c5..7d1bdb0 100644
--- a/tests/tests/permission/nativeTests/AndroidTest.xml
+++ b/tests/tests/permission/nativeTests/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
diff --git a/tests/tests/permission/sdk28/AndroidTest.xml b/tests/tests/permission/sdk28/AndroidTest.xml
index e47b1ac..d1b5ac6 100644
--- a/tests/tests/permission/sdk28/AndroidTest.xml
+++ b/tests/tests/permission/sdk28/AndroidTest.xml
@@ -20,6 +20,7 @@
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsPermissionTestCasesSdk28.apk" />
diff --git a/tests/tests/permission/src/android/permission/cts/AppIdleStatePermissionTest.java b/tests/tests/permission/src/android/permission/cts/AppIdleStatePermissionTest.java
index c3631bc..bba9963 100644
--- a/tests/tests/permission/src/android/permission/cts/AppIdleStatePermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/AppIdleStatePermissionTest.java
@@ -63,4 +63,30 @@
+ pkgNames);
}
}
+
+ /**
+ * Verify that the {@link android.Manifest.permission#CHANGE_APP_LAUNCH_TIME_ESTIMATE}
+ * permission is only held by at most one package.
+ */
+ @Test
+ public void testChangeAppLaunchEstimatePermission() throws Exception {
+ final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+ final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[]{
+ android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE
+ }, PackageManager.MATCH_SYSTEM_ONLY);
+
+ int count = 0;
+ String pkgNames = "";
+ for (PackageInfo pkg : holding) {
+ int uid = pm.getApplicationInfo(pkg.packageName, 0).uid;
+ if (UserHandle.isApp(uid)) {
+ pkgNames += pkg.packageName + "\n";
+ count++;
+ }
+ }
+ if (count > 1) {
+ fail("Only one app may hold the CHANGE_APP_LAUNCH_TIME_ESTIMATE permission;"
+ + " found packages: \n" + pkgNames);
+ }
+ }
}
\ No newline at end of file
diff --git a/tests/tests/permission/src/android/permission/cts/SelfRevokeRuntimePermissionTest.java b/tests/tests/permission/src/android/permission/cts/SelfRevokeRuntimePermissionTest.java
new file mode 100644
index 0000000..ce1d554
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/SelfRevokeRuntimePermissionTest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.CAMERA;
+import static android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.permission.cts.PermissionUtils.getPermissionFlags;
+import static android.permission.cts.PermissionUtils.grantPermission;
+import static android.permission.cts.PermissionUtils.setPermissionFlags;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.provider.DeviceConfig;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SelfRevokeRuntimePermissionTest {
+ private static final String APP_PKG_NAME =
+ "android.permission.cts.apptotestselfrevokepermission";
+ private static final String APK =
+ "/data/local/tmp/cts/permissions/CtsAppToTestSelfRevokePermission.apk";
+ private static final long ONE_TIME_TIMEOUT_MILLIS = 500;
+ private static final long KILLED_DELAY_MILLIS = 100;
+ private static final long ONE_TIME_TIMER_UPPER_GRACE_PERIOD = 1000;
+
+ private final Instrumentation mInstrumentation =
+ InstrumentationRegistry.getInstrumentation();
+ private final Context mContext = mInstrumentation.getTargetContext();
+ private final ActivityManager mActivityManager =
+ mContext.getSystemService(ActivityManager.class);
+ private final UiDevice mUiDevice = UiDevice.getInstance(mInstrumentation);
+ private String mOldOneTimePermissionTimeoutValue;
+
+ @Before
+ public void wakeUpScreen() {
+ SystemUtil.runShellCommand("input keyevent KEYCODE_WAKEUP");
+ SystemUtil.runShellCommand("input keyevent 82");
+ }
+
+ @Before
+ public void prepareDeviceForOneTime() {
+ runWithShellPermissionIdentity(() -> {
+ mOldOneTimePermissionTimeoutValue = DeviceConfig.getProperty("permissions",
+ "one_time_permissions_timeout_millis");
+ DeviceConfig.setProperty("permissions", "one_time_permissions_timeout_millis",
+ Long.toString(ONE_TIME_TIMEOUT_MILLIS), false);
+ DeviceConfig.setProperty("permissions", "one_time_permissions_killed_delay_millis",
+ Long.toString(KILLED_DELAY_MILLIS), false);
+ });
+ }
+
+ @After
+ public void uninstallApp() {
+ runShellCommand("pm uninstall " + APP_PKG_NAME);
+ }
+
+ @After
+ public void restoreDeviceForOneTime() {
+ runWithShellPermissionIdentity(
+ () -> DeviceConfig.setProperty("permissions", "one_time_permissions_timeout_millis",
+ mOldOneTimePermissionTimeoutValue, false));
+ }
+
+ @Test
+ public void testMultiplePermissions() throws Throwable {
+ // Trying to revoke multiple permissions including some from the same permission group
+ // should work.
+ installApp();
+ String[] permissions = new String[] {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION, CAMERA};
+ for (String permission : permissions) {
+ grantPermission(APP_PKG_NAME, permission);
+ }
+ revokePermissions(permissions);
+ placeAppInBackground();
+ for (String permission : permissions) {
+ assertDenied(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ permission);
+ }
+ uninstallApp();
+ }
+
+ @Test
+ public void testNormalPermission() throws Throwable {
+ // Trying to revoke a normal (non-runtime) permission should not actually revoke it.
+ installApp();
+ revokePermission(HIGH_SAMPLING_RATE_SENSORS);
+ placeAppInBackground();
+ try {
+ waitUntilPermissionRevoked(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ HIGH_SAMPLING_RATE_SENSORS);
+ fail("android.permission.HIGH_SAMPLING_RATE_SENSORS was revoked");
+ } catch (Throwable expected) {
+ assertEquals(HIGH_SAMPLING_RATE_SENSORS + " not revoked",
+ expected.getMessage());
+ }
+ uninstallApp();
+ }
+
+ @Test
+ public void testKillTriggersRevocation() throws Throwable {
+ // Killing the process should start the revocation right away
+ installApp();
+ grantPermission(APP_PKG_NAME, ACCESS_FINE_LOCATION);
+ revokePermission(ACCESS_FINE_LOCATION);
+ killApp();
+ assertDenied(KILLED_DELAY_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ ACCESS_FINE_LOCATION);
+ uninstallApp();
+ }
+
+ @Test
+ public void testNoRevocationWhileForeground() throws Throwable {
+ // Even after calling selfRevokePermission, the permission should stay granted while the
+ // package is in the foreground.
+ installApp();
+ grantPermission(APP_PKG_NAME, ACCESS_FINE_LOCATION);
+ revokePermission(ACCESS_FINE_LOCATION);
+ try {
+ waitUntilPermissionRevoked(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ ACCESS_FINE_LOCATION);
+ fail("android.permission.ACCESS_FINE_LOCATION was revoked");
+ } catch (Throwable expected) {
+ assertEquals(ACCESS_FINE_LOCATION + " not revoked",
+ expected.getMessage());
+ }
+ uninstallApp();
+ }
+
+ @Test
+ public void testWhileInUseLocationPermission() throws Throwable {
+ // After revoking any location permission and leaving the app in background for a while, the
+ // location permission group should be revoked.
+ installApp();
+ grantPermission(APP_PKG_NAME, ACCESS_COARSE_LOCATION);
+ grantPermission(APP_PKG_NAME, ACCESS_FINE_LOCATION);
+ revokePermission(ACCESS_FINE_LOCATION);
+ placeAppInBackground();
+ assertDenied(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ ACCESS_FINE_LOCATION);
+ assertDenied(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ ACCESS_COARSE_LOCATION);
+ uninstallApp();
+ }
+
+ @Test
+ public void testNoRepromptWhenUserFixed() throws Throwable {
+ // If a permission has been USER_FIXED to not granted, then revoking the permission group
+ // should leave the USER_FIXED flag.
+ installApp();
+ grantPermission(APP_PKG_NAME, ACCESS_FINE_LOCATION);
+ setPermissionFlags(APP_PKG_NAME, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_FIXED,
+ FLAG_PERMISSION_USER_FIXED);
+ revokePermission(ACCESS_FINE_LOCATION);
+ placeAppInBackground();
+ assertDenied(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ ACCESS_FINE_LOCATION);
+ int flags = getPermissionFlags(APP_PKG_NAME, ACCESS_BACKGROUND_LOCATION);
+ assertEquals(FLAG_PERMISSION_USER_FIXED, flags & FLAG_PERMISSION_USER_FIXED);
+ uninstallApp();
+ }
+
+
+ private void installApp() {
+ runShellCommand("pm install -r " + APK);
+ }
+
+ private void placeAppInBackground() {
+ boolean[] hasExited = {false};
+ try {
+ new Thread(() -> {
+ while (!hasExited[0]) {
+ mUiDevice.pressHome();
+ mUiDevice.pressBack();
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ }
+ }
+ }).start();
+ eventually(() -> {
+ runWithShellPermissionIdentity(() -> {
+ if (mActivityManager.getPackageImportance(APP_PKG_NAME)
+ <= IMPORTANCE_FOREGROUND) {
+ throw new AssertionError("Unable to exit application");
+ }
+ });
+ });
+ } finally {
+ hasExited[0] = true;
+ }
+ }
+
+ /**
+ * Start the app. The app will revoke the permission.
+ */
+ private void revokePermission(String permName) {
+ revokePermissions(new String[] { permName });
+ }
+
+ private void revokePermissions(String[] permissions) {
+ runShellCommand("am start-activity -W -n " + APP_PKG_NAME + "/.RevokePermission"
+ + " --esa permissions " + String.join(",", permissions));
+ }
+
+ private void killApp() {
+ runShellCommand("am force-stop " + APP_PKG_NAME);
+ }
+
+ private void assertGrantedState(String s, String permissionName, int permissionGranted,
+ long timeoutMillis) {
+ eventually(() -> Assert.assertEquals(s, permissionGranted,
+ mContext.getPackageManager().checkPermission(permissionName, APP_PKG_NAME)),
+ timeoutMillis);
+ }
+
+ private void assertGranted(long timeoutMillis, String permissionName) {
+ assertGrantedState("Permission was never granted", permissionName,
+ PackageManager.PERMISSION_GRANTED, timeoutMillis);
+ }
+
+ private void assertDenied(long timeoutMillis, String permissionName) {
+ assertGrantedState("Permission was never revoked", permissionName,
+ PackageManager.PERMISSION_DENIED, timeoutMillis);
+ }
+
+ private void waitUntilPermissionRevoked(long timeoutMillis, String permName) throws Throwable {
+ try {
+ eventually(() -> {
+ PackageInfo appInfo = mContext.getPackageManager().getPackageInfo(APP_PKG_NAME,
+ GET_PERMISSIONS);
+
+ for (int i = 0; i < appInfo.requestedPermissions.length; i++) {
+ if (appInfo.requestedPermissions[i].equals(permName)
+ && (
+ (appInfo.requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED)
+ == 0)) {
+ return;
+ }
+ }
+
+ fail(permName + " not revoked");
+ }, timeoutMillis);
+ } catch (RuntimeException e) {
+ throw e.getCause();
+ }
+ }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java b/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
index 0016704..63cd399 100755
--- a/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
+++ b/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
@@ -24,6 +24,8 @@
import static android.Manifest.permission.BLUETOOTH_ADMIN;
import static android.Manifest.permission.BLUETOOTH_CONNECT;
import static android.Manifest.permission.BLUETOOTH_SCAN;
+import static android.Manifest.permission.BODY_SENSORS;
+import static android.Manifest.permission.BODY_SENSORS_BACKGROUND;
import static android.Manifest.permission.READ_CALL_LOG;
import static android.Manifest.permission.READ_CONTACTS;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
@@ -125,10 +127,13 @@
// STOPSHIP(b/184180558): replace with "S" once SDK is finalized
assertSplit(split, Build.VERSION_CODES.R + 1, BLUETOOTH, BLUETOOTH_ADMIN);
break;
+ case BODY_SENSORS:
+ // STOPSHIP(b/212583342): replace with "T" once SDK is finalized
+ assertSplit(split, Build.VERSION_CODES.S_V2 + 1, BODY_SENSORS_BACKGROUND);
}
}
- assertEquals(13, seenSplits.size());
+ assertEquals(14, seenSplits.size());
}
private void assertSplit(SplitPermissionInfo split, int targetSdk, String... permission) {
diff --git a/tests/tests/permission/telephony/AndroidTest.xml b/tests/tests/permission/telephony/AndroidTest.xml
index cdf5ff1..3a8dc5e 100644
--- a/tests/tests/permission/telephony/AndroidTest.xml
+++ b/tests/tests/permission/telephony/AndroidTest.xml
@@ -21,6 +21,7 @@
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<!-- Install main test suite apk -->
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/permission2/AndroidTest.xml b/tests/tests/permission2/AndroidTest.xml
index cc1bc7f..54ea94b 100644
--- a/tests/tests/permission2/AndroidTest.xml
+++ b/tests/tests/permission2/AndroidTest.xml
@@ -20,6 +20,7 @@
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
<option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index b4dbf59..a1ab5cc 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -55,6 +55,7 @@
<protected-broadcast android:name="android.intent.action.PACKAGE_VERIFIED" />
<protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENDED" />
<protected-broadcast android:name="android.intent.action.PACKAGES_UNSUSPENDED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENSION_CHANGED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY" />
<protected-broadcast android:name="android.intent.action.DISTRACTING_PACKAGES_CHANGED" />
<protected-broadcast android:name="android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED" />
@@ -63,6 +64,7 @@
<protected-broadcast android:name="android.intent.action.CONFIGURATION_CHANGED" />
<protected-broadcast android:name="android.intent.action.SPLIT_CONFIGURATION_CHANGED" />
<protected-broadcast android:name="android.intent.action.LOCALE_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.APPLICATION_LOCALE_CHANGED" />
<protected-broadcast android:name="android.intent.action.BATTERY_CHANGED" />
<protected-broadcast android:name="android.intent.action.BATTERY_LEVEL_CHANGED" />
<protected-broadcast android:name="android.intent.action.BATTERY_LOW" />
@@ -201,6 +203,11 @@
android:name="android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED" />
<protected-broadcast
android:name="android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.CSIS_CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.CSIS_DEVICE_AVAILABLE" />
+ <protected-broadcast android:name="android.bluetooth.action.CSIS_SET_MEMBER_AVAILABLE" />
+ <protected-broadcast
+ android:name="android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast
android:name="android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast
@@ -247,6 +254,11 @@
android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY" />
<protected-broadcast
android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_CONF_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_GROUP_NODE_STATUS_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_GROUP_STATUS_CHANGED" />
<protected-broadcast
android:name="android.bluetooth.action.TETHERING_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED" />
@@ -550,6 +562,8 @@
<protected-broadcast android:name="com.android.server.telecom.intent.action.CALLS_ADD_ENTRY" />
<protected-broadcast android:name="com.android.settings.location.MODE_CHANGING" />
<protected-broadcast android:name="com.android.settings.bluetooth.ACTION_DISMISS_PAIRING" />
+ <protected-broadcast android:name="com.android.settings.network.DELETE_SUBSCRIPTION" />
+ <protected-broadcast android:name="com.android.settings.network.SWITCH_TO_SUBSCRIPTION" />
<protected-broadcast android:name="com.android.settings.wifi.action.NETWORK_REQUEST" />
<protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
@@ -616,7 +630,11 @@
<protected-broadcast android:name="com.android.server.fingerprint.ACTION_LOCKOUT_RESET" />
<protected-broadcast android:name="android.net.wifi.PASSPOINT_ICON_RECEIVED" />
+
<protected-broadcast android:name="com.android.server.notification.CountdownConditionProvider" />
+ <protected-broadcast android:name="android.server.notification.action.ENABLE_NAS" />
+ <protected-broadcast android:name="android.server.notification.action.DISABLE_NAS" />
+ <protected-broadcast android:name="android.server.notification.action.LEARNMORE_NAS" />
<protected-broadcast android:name="com.android.internal.location.ALARM_WAKEUP" />
<protected-broadcast android:name="com.android.internal.location.ALARM_TIMEOUT" />
@@ -693,8 +711,8 @@
<!-- Added in S -->
<protected-broadcast android:name="android.scheduling.action.REBOOT_READY" />
<protected-broadcast android:name="android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED" />
-
<protected-broadcast android:name="android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.app.action.SHOW_NEW_USER_DISCLAIMER" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
@@ -738,7 +756,16 @@
android:permissionGroup="android.permission-group.UNDEFINED"
android:label="@string/permlab_writeContacts"
android:description="@string/permdesc_writeContacts"
- android:protectionLevel="dangerous" />
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to set default account for new contacts.
+ <p> This permission is only granted to system applications fulfilling the Contacts app role.
+ <p>Protection level: internal|role
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS"
+ android:protectionLevel="internal|role" />
<!-- ====================================================================== -->
<!-- Permissions for accessing user's calendar -->
@@ -1451,10 +1478,28 @@
measure what is happening inside their body, such as heart rate.
<p>Protection level: dangerous -->
<permission android:name="android.permission.BODY_SENSORS"
- android:permissionGroup="android.permission-group.UNDEFINED"
- android:label="@string/permlab_bodySensors"
- android:description="@string/permdesc_bodySensors"
- android:protectionLevel="dangerous" />
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_bodySensors"
+ android:description="@string/permdesc_bodySensors"
+ android:backgroundPermission="android.permission.BODY_SENSORS_BACKGROUND"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to access data from sensors that the user uses to measure what is
+ happening inside their body, such as heart rate. If you're requesting this permission, you
+ must also request {@link #BODY_SENSORS}. Requesting this permission by itself doesn't give
+ you Body sensors access.
+ <p>Protection level: dangerous
+
+ <p> This is a hard restricted permission which cannot be held by an app until
+ the installer on record allowlists the permission. For more details see
+ {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+ -->
+ <permission android:name="android.permission.BODY_SENSORS_BACKGROUND"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_bodySensors_background"
+ android:description="@string/permdesc_bodySensors_background"
+ android:protectionLevel="dangerous"
+ android:permissionFlags="hardRestricted" />
<!-- Allows an app to use fingerprint hardware.
<p>Protection level: normal
@@ -1476,6 +1521,28 @@
android:description="@string/permdesc_useBiometric"
android:protectionLevel="normal" />
+ <!-- ======================================================================= -->
+ <!-- Permissions for posting notifications -->
+ <!-- ====================================================================== -->
+ <eat-comment />
+
+ <!-- Used for permissions that are associated with posting notifications
+ -->
+ <permission-group android:name="android.permission-group.NOTIFICATIONS"
+ android:icon="@drawable/ic_notifications_alerted"
+ android:label="@string/permgrouplab_notifications"
+ android:description="@string/permgroupdesc_notifications"
+ android:priority="850" />
+
+ <!-- Allows an app to post notifications
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.POST_NOTIFICATIONS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_postNotification"
+ android:description="@string/permdesc_postNotification"
+ android:protectionLevel="dangerous" />
+
<!-- ====================================================================== -->
<!-- REMOVED PERMISSIONS -->
<!-- ====================================================================== -->
@@ -1702,6 +1769,13 @@
<permission android:name="android.permission.ACCESS_MOCK_LOCATION"
android:protectionLevel="signature" />
+ <!-- @SystemApi @hide Allows automotive applications to control location
+ suspend state for power management use cases.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.AUTOMOTIVE_GNSS_CONTROLS"
+ android:protectionLevel="signature|privileged" />
+
<!-- ======================================= -->
<!-- Permissions for accessing networks -->
<!-- ======================================= -->
@@ -1739,6 +1813,13 @@
android:label="@string/permlab_changeWifiState"
android:protectionLevel="normal" />
+ <!-- @SystemApi @hide Allows applications to enable/disable wifi auto join. This permission
+ is used to let OEMs grant their trusted app access to a subset of privileged wifi APIs
+ to improve wifi performance.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_WIFI_AUTO_JOIN"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi @hide Allows apps to create and manage IPsec tunnels.
<p>Only granted to applications that are currently bound by the
system for creating and managing IPsec-based interfaces.
@@ -1851,7 +1932,7 @@
@hide This should only be used by ManagedProvisioning app.
-->
<permission android:name="android.permission.NETWORK_MANAGED_PROVISIONING"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|role" />
<!-- Allows Carrier Provisioning to call methods in Networking services
<p>Not for use by any other third-party or privileged applications.
@@ -1968,10 +2049,10 @@
<!-- Required to be able to advertise and connect to nearby devices via Wi-Fi.
<p>Protection level: dangerous -->
<permission android:name="android.permission.NEARBY_WIFI_DEVICES"
- android:permissionGroup="android.permission-group.UNDEFINED"
- android:description="@string/permdesc_nearby_wifi_devices"
- android:label="@string/permlab_nearby_wifi_devices"
- android:protectionLevel="dangerous" />
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:description="@string/permdesc_nearby_wifi_devices"
+ android:label="@string/permlab_nearby_wifi_devices"
+ android:protectionLevel="dangerous" />
<!-- @SystemApi @TestApi Allows an application to suspend other apps, which will prevent the
user from using them until they are unsuspended.
@@ -2307,10 +2388,10 @@
<permission android:name="android.permission.OEM_UNLOCK_STATE"
android:protectionLevel="signature" />
- <!-- @hide Allows querying state of PersistentDataBlock
+ <!-- @SystemApi @hide Allows querying state of PersistentDataBlock
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.ACCESS_PDB_STATE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|role" />
<!-- Allows testing if a passwords is forbidden by the admins.
@hide <p>Not for use by third-party applications. -->
@@ -2368,7 +2449,7 @@
<!-- @SystemApi @TestApi Allows read access to privileged phone state.
@hide Used internally. -->
<permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|role" />
<!-- Allows to read device identifiers and use ICC based authentication like EAP-AKA.
Often required in authentication to access the carrier's server and manage services
@@ -2383,7 +2464,7 @@
<permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"
android:protectionLevel="signature" />
- <!-- Allows listen permission to always reported signal strength.
+ <!-- Allows listen permission to always reported system signal strength.
@hide Used internally. -->
<permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH"
android:protectionLevel="signature" />
@@ -2593,7 +2674,7 @@
third-party apps.
-->
<permission android:name="android.permission.MANAGE_DOCUMENTS"
- android:protectionLevel="signature|documenter" />
+ android:protectionLevel="signature|role" />
<!-- Allows an application to manage access to crates, usually as part
of a crates picker.
@@ -2610,7 +2691,7 @@
<p>Not for use by third-party applications.
-->
<permission android:name="android.permission.CACHE_CONTENT"
- android:protectionLevel="signature|documenter" />
+ android:protectionLevel="signature|role" />
<!-- @SystemApi @hide
Allows an application to aggressively allocate disk space.
@@ -2684,25 +2765,25 @@
user-targeted broadcasts. This permission is not available to
third party applications. -->
<permission android:name="android.permission.INTERACT_ACROSS_USERS"
- android:protectionLevel="signature|privileged|development" />
+ android:protectionLevel="signature|privileged|development|role" />
<!-- @SystemApi Fuller form of {@link android.Manifest.permission#INTERACT_ACROSS_USERS}
that removes restrictions on where broadcasts can be sent and allows other
types of interactions
@hide -->
<permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
- android:protectionLevel="signature|installer" />
+ android:protectionLevel="signature|installer|role" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<!-- Allows interaction across profiles in the same profile group. -->
<permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
android:protectionLevel="signature|appop" />
- <!-- Allows configuring apps to have the INTERACT_ACROSS_PROFILES permission so that they can
- interact across profiles in the same profile group.
+ <!-- @SystemApi Allows configuring apps to have the INTERACT_ACROSS_PROFILES permission so that
+ they can interact across profiles in the same profile group.
@hide -->
<permission android:name="android.permission.CONFIGURE_INTERACT_ACROSS_PROFILES"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|role" />
<!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. -->
<permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"
@@ -2723,17 +2804,27 @@
<permission android:name="android.permission.CREATE_USERS"
android:protectionLevel="signature" />
+ <!-- @SystemApi @hide Allows an application to call APIs that allow it to query users on the
+ device. -->
+ <permission android:name="android.permission.QUERY_USERS"
+ android:protectionLevel="signature|role" />
+
<!-- Allows an application to access data blobs across users. -->
<permission android:name="android.permission.ACCESS_BLOBS_ACROSS_USERS"
android:protectionLevel="signature|privileged|development|role" />
- <!-- @hide Allows an application to set the profile owners and the device owner.
+ <!-- @SystemApi @hide Allows an application to set the profile owners and the device owner.
This permission is not available to third party applications.-->
<permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"
- android:protectionLevel="signature"
+ android:protectionLevel="signature|role|setup"
android:label="@string/permlab_manageProfileAndDeviceOwners"
android:description="@string/permdesc_manageProfileAndDeviceOwners" />
+ <!-- @SystemApi @hide Allows an application to query device policies set by any admin on
+ the device.-->
+ <permission android:name="android.permission.QUERY_ADMIN_POLICY"
+ android:protectionLevel="signature|role" />
+
<!-- @TestApi @hide Allows an application to reset the record of previous system update freeze
periods. -->
<permission android:name="android.permission.CLEAR_FREEZE_PERIOD"
@@ -2760,7 +2851,7 @@
<!-- @SystemApi @TestApi @hide Allows an application to change to remove/kill tasks -->
<permission android:name="android.permission.REMOVE_TASKS"
- android:protectionLevel="signature|documenter|recents" />
+ android:protectionLevel="signature|recents|role" />
<!-- @deprecated Use MANAGE_ACTIVITY_TASKS instead.
@SystemApi @TestApi @hide Allows an application to create/manage/remove stacks -->
@@ -2783,7 +2874,7 @@
<!-- @SystemApi @hide Allows an application to start activities from background -->
<permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"
- android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier" />
+ android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier|role" />
<!-- Allows an application to start foreground services from the background at any time.
<em>This permission is not for use by third-party applications</em>,
@@ -2897,7 +2988,13 @@
<permission android:name="android.permission.SET_DISPLAY_OFFSET"
android:protectionLevel="signature|privileged" />
- <!-- Allows a companion app to run in the background.
+ <!-- Allows a companion app to run in the background. This permission implies
+ {@link android.Manifest.permission#REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND},
+ and allows to start a foreground service from the background.
+ If an app does not have to run in the background, but only needs to start a foreground
+ service from the background, consider using
+ {@link android.Manifest.permission#REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND},
+ which is less powerful.
<p>Protection level: normal
-->
<permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND"
@@ -2907,6 +3004,7 @@
<!-- Allows a companion app to start a foreground service from the background.
{@see android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND}
+ <p>Protection level: normal
-->
<permission android:name="android.permission.REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND"
android:protectionLevel="normal"/>
@@ -2927,6 +3025,35 @@
<permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH"
android:protectionLevel="normal" />
+ <!-- Allows application to request to be associated with a virtual display capable of streaming
+ Android applications
+ ({@link android.companion.AssociationRequest#DEVICE_PROFILE_APP_STREAMING})
+ by {@link android.companion.CompanionDeviceManager}.
+ <p>Not for use by third-party applications.
+ @hide
+ @SystemApi
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows application to request to be associated with a vehicle head unit capable of
+ automotive projection
+ ({@link android.companion.AssociationRequest#DEVICE_PROFILE_AUTOMOTIVE_PROJECTION})
+ by {@link android.companion.CompanionDeviceManager}.
+ <p>Not for use by third-party applications.
+ @hide
+ @SystemApi
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to create a "self-managed" association.
+ @hide
+ @SystemApi
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows a companion app to associate to Wi-Fi.
<p>Only for use by a single pre-approved app.
@hide
@@ -2943,13 +3070,11 @@
android:protectionLevel="signature" />
<!-- Allows an app to set and release automotive projection.
- <p>Once permissions can be granted via role-only, this needs to be changed to
- protectionLevel="role" and added to the SYSTEM_AUTOMOTIVE_PROJECTION role.
@hide
@SystemApi
-->
<permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="internal|role" />
<!-- Allows an app to prevent non-system-overlay windows from being drawn on top of it -->
<permission android:name="android.permission.HIDE_OVERLAY_WINDOWS"
@@ -2993,7 +3118,7 @@
<!-- Allows applications to set the system time directly.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.SET_TIME"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|role" />
<!-- Allows applications to set the system time zone directly.
<p>Not for use by third-party applications.
@@ -3001,7 +3126,7 @@
<permission android:name="android.permission.SET_TIME_ZONE"
android:label="@string/permlab_setTimeZone"
android:description="@string/permdesc_setTimeZone"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|role" />
<!-- Allows telephony to suggest the time / time zone.
<p>Not for use by third-party applications.
@@ -3115,7 +3240,7 @@
as locale.
<p>Protection level: signature|privileged|development -->
<permission android:name="android.permission.CHANGE_CONFIGURATION"
- android:protectionLevel="signature|privileged|development" />
+ android:protectionLevel="signature|privileged|development|role" />
<!-- Allows an application to read or write the system settings.
@@ -3132,7 +3257,7 @@
<permission android:name="android.permission.WRITE_SETTINGS"
android:label="@string/permlab_writeSettings"
android:description="@string/permdesc_writeSettings"
- android:protectionLevel="signature|preinstalled|appop|pre23" />
+ android:protectionLevel="signature|preinstalled|appop|pre23|role" />
<!-- Allows an application to modify the Google service map.
<p>Not for use by third-party applications. -->
@@ -3149,6 +3274,12 @@
<permission android:name="android.permission.READ_DEVICE_CONFIG"
android:protectionLevel="signature|preinstalled" />
+ <!-- @SystemApi @hide Allows applications like settings to read system-owned
+ application-specific locale configs.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES"
+ android:protectionLevel="signature" />
+
<!-- @hide Allows an application to monitor {@link android.provider.Settings.Config} access.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"
@@ -3371,6 +3502,23 @@
<permission android:name="android.permission.UPDATE_FONTS"
android:protectionLevel="signature|privileged" />
+ <!-- Allows an application to use the AttestationVerificationService.
+ @hide -->
+ <permission android:name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to export a AttestationVerificationService to verify attestations on
+ behalf of AttestationVerificationManager for system-defined attestation profiles.
+ @hide -->
+ <permission android:name="android.permission.VERIFY_ATTESTATION"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by any AttestationVerificationService to ensure that only the system can
+ bind to it.
+ @hide -->
+ <permission android:name="android.permission.BIND_ATTESTATION_VERIFICATION_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- ========================================= -->
<!-- Permissions for special development tools -->
<!-- ========================================= -->
@@ -3379,7 +3527,7 @@
<!-- Allows an application to read or write the secure system settings.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.WRITE_SECURE_SETTINGS"
- android:protectionLevel="signature|privileged|development" />
+ android:protectionLevel="signature|privileged|development|role|installer" />
<!-- Allows an application to retrieve state dump information from system services.
<p>Not for use by third-party applications. -->
@@ -3434,6 +3582,16 @@
<permission android:name="android.permission.REQUEST_INCIDENT_REPORT_APPROVAL"
android:protectionLevel="signature|privileged" />
+ <!-- ========================================= -->
+ <!-- Permissions for SupplementalApi -->
+ <!-- ========================================= -->
+ <eat-comment />
+
+ <!-- TODO(b/213488783): Update with correct names. -->
+ <!-- Allows an application to access SupplementalApis. -->
+ <permission android:name="android.permission.ACCESS_SUPPLEMENTAL_APIS"
+ android:protectionLevel="normal" />
+
<!-- ==================================== -->
<!-- Private permissions -->
<!-- ==================================== -->
@@ -3511,6 +3669,13 @@
<permission android:name="android.permission.GET_APP_OPS_STATS"
android:protectionLevel="signature|privileged|development" />
+ <!-- @SystemApi @hide Allows an application to collect historical application operation
+ statistics.
+ <p>Not for use by third party applications.
+ -->
+ <permission android:name="android.permission.GET_HISTORICAL_APP_OPS_STATS"
+ android:protectionLevel="internal|role" />
+
<!-- @SystemApi Allows an application to update application operation statistics. Not for
use by third party apps.
@hide -->
@@ -3632,7 +3797,7 @@
to put the higher-level system there into a shutdown state.
@hide -->
<permission android:name="android.permission.SHUTDOWN"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|role" />
<!-- @SystemApi Allows an application to tell the activity manager to temporarily
stop application switches, putting it into a special mode that
@@ -3650,6 +3815,13 @@
<permission android:name="android.permission.GET_TOP_ACTIVITY_INFO"
android:protectionLevel="signature|recents" />
+ <!-- @SystemApi Allows an application to set the system audio caption and its UI
+ enabled state.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION"
+ android:protectionLevel="signature|role" />
+
<!-- Allows an application to retrieve the current state of keys and
switches.
<p>Not for use by third-party applications.
@@ -3786,6 +3958,15 @@
<permission android:name="android.permission.BIND_WALLPAPER"
android:protectionLevel="signature|privileged" />
+ <!-- Must be required by a game service to ensure that only the
+ system can bind to it.
+ <p>Protection level: signature
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_GAME_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- Must be required by a {@link android.service.voice.VoiceInteractionService},
to ensure that only the system can bind to it.
<p>Protection level: signature
@@ -3846,6 +4027,14 @@
<permission android:name="android.permission.BIND_TEXTCLASSIFIER_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be required by a android.service.selectiontoolbar.SelectionToolbarRenderService,
+ to ensure that only the system can bind to it.
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_SELECTION_TOOLBAR_RENDER_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- Must be required by a android.service.contentcapture.ContentCaptureService,
to ensure that only the system can bind to it.
@SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -4003,14 +4192,13 @@
<p>Protection level: signature
-->
<permission android:name="android.permission.BIND_DEVICE_ADMIN"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|role" />
<!-- @SystemApi Required to add or remove another application as a device admin.
<p>Not for use by third-party applications.
- @hide
- @removed -->
+ @hide -->
<permission android:name="android.permission.MANAGE_DEVICE_ADMINS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|role" />
<!-- @SystemApi Allows an app to reset the device password.
<p>Not for use by third-party applications.
@@ -4060,8 +4248,16 @@
<permission android:name="android.permission.SCHEDULE_PRIORITIZED_ALARM"
android:protectionLevel="signature|privileged"/>
- <!-- Allows an app to use exact alarm scheduling APIs to perform timing
- sensitive background work.
+ <!-- Allows applications to use exact alarm APIs.
+ <p>Exact alarms should only be used for user-facing features.
+ For more details, see <a
+ href="{@docRoot}about/versions/12/behavior-changes-12#exact-alarm-permission">
+ Exact alarm permission</a>.
+ <p>Apps who hold this permission and target API level 31 or above, always stay in the
+ {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_WORKING_SET WORKING_SET} or
+ lower standby bucket.
+ Applications targeting API level 30 or below do not need this permission to use
+ exact alarm APIs.
-->
<permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
android:protectionLevel="normal|appop"/>
@@ -4115,14 +4311,14 @@
<permission android:name="android.permission.INSTALL_PACKAGE_UPDATES"
android:protectionLevel="signature|privileged" />
- <!-- Allows an application to install existing system packages. This is a limited
+ <!-- @SystemApi Allows an application to install existing system packages. This is a limited
version of {@link android.Manifest.permission#INSTALL_PACKAGES}.
<p>Not for use by third-party applications.
TODO(b/80204953): remove this permission once we have a long-term solution.
@hide
-->
<permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|role" />
<!-- Allows an application to use the package installer v2 APIs.
<p>The package installer v2 APIs are still a work in progress and we're
@@ -4140,6 +4336,16 @@
<permission android:name="android.permission.INSTALL_TEST_ONLY_PACKAGE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to install DPCs only, an application is
+ considered a DPC if it has a {@link android.app.admin.DeviceAdminReceiver}
+ protected by {@link android.Manifest.permission#BIND_DEVICE_ADMIN).
+ This is a limited version of
+ {@link android.Manifest.permission#INSTALL_PACKAGES}.
+ @hide
+ -->
+ <permission android:name="android.permission.INSTALL_DPC_PACKAGES"
+ android:protectionLevel="signature|role" />
+
<!-- Allows an application to use System Data Loaders.
<p>Not for use by third-party applications.
@hide
@@ -4207,7 +4413,7 @@
when the application deleting the package is not the same application that installed the
package. -->
<permission android:name="android.permission.DELETE_PACKAGES"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|role" />
<!-- @SystemApi Allows an application to move location of installed package.
@hide -->
@@ -4223,7 +4429,7 @@
enabled or not.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|role" />
<!-- @SystemApi Allows an application to grant specific permissions.
@hide -->
@@ -4241,6 +4447,12 @@
<permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS"
android:protectionLevel="signature|installer|verifier" />
+ <!-- @TestApi Allows an application to revoke the POST_NOTIFICATIONS permission from an app
+ without killing the app. Only granted to the shell.
+ @hide -->
+ <permission android:name="android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows the system to read runtime permission state.
@hide -->
<permission android:name="android.permission.GET_RUNTIME_PERMISSIONS"
@@ -4314,6 +4526,11 @@
<permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE"
android:protectionLevel="normal" />
+ <!-- Allows an application to deliver companion messages to system
+ -->
+ <permission android:name="android.permission.DELIVER_COMPANION_MESSAGES"
+ android:protectionLevel="normal" />
+
<!-- Allows an application to create new companion device associations.
@SystemApi
@hide -->
@@ -4436,6 +4653,12 @@
<permission android:name="android.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE"
android:protectionLevel="signature" />
+ <!-- Allows an application to modify the user preferred display mode.
+ @hide
+ @TestApi -->
+ <permission android:name="android.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to control VPN.
<p>Not for use by third-party applications.</p>
@hide -->
@@ -4506,6 +4729,12 @@
<permission android:name="android.permission.CAPTURE_AUDIO_HOTWORD"
android:protectionLevel="signature|privileged|role" />
+ <!-- @SystemApi Allows an application to access the ultrasound content.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.ACCESS_ULTRASOUND"
+ android:protectionLevel="signature|privileged" />
+
<!-- Puts an application in the chain of trust for sound trigger
operations. Being in the chain of trust allows an application to
delegate an identity of a separate entity to the sound trigger system
@@ -4521,6 +4750,13 @@
<permission android:name="android.permission.MODIFY_AUDIO_ROUTING"
android:protectionLevel="signature|privileged|role" />
+ <!-- @SystemApi Allows an application to access the uplink and downlink audio of an ongoing
+ call.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.CALL_AUDIO_INTERCEPTION"
+ android:protectionLevel="signature|privileged" />
+
<!-- @TestApi Allows an application to query audio related state.
@hide -->
<permission android:name="android.permission.QUERY_AUDIO_STATE"
@@ -4664,7 +4900,7 @@
<!-- Not for use by third-party applications. -->
<permission android:name="android.permission.MASTER_CLEAR"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|role" />
<!-- Allows an application to call any phone number, including emergency
numbers, without going through the Dialer user interface for the user
@@ -4675,7 +4911,7 @@
<!-- @SystemApi Allows an application to perform CDMA OTA provisioning @hide -->
<permission android:name="android.permission.PERFORM_CDMA_PROVISIONING"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|role" />
<!-- @SystemApi Allows an application to perform SIM Activation @hide -->
<permission android:name="android.permission.PERFORM_SIM_ACTIVATION"
@@ -4724,6 +4960,12 @@
<permission android:name="android.permission.CHANGE_APP_IDLE_STATE"
android:protectionLevel="signature|privileged" />
+ <!-- @hide @TestApi @SystemApi Allows an application to change the estimated launch time
+ of an app.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE"
+ android:protectionLevel="signature|privileged" />
+
<!-- @hide @SystemApi Allows an application to temporarily allowlist an inactive app to
access the network and acquire wakelocks.
<p>Not for use by third-party applications. -->
@@ -4902,7 +5144,7 @@
@hide
-->
<permission android:name="android.permission.CRYPT_KEEPER"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|role" />
<!-- @SystemApi Allows an application to read historical network usage for
specific networks and applications. @hide -->
@@ -5047,10 +5289,10 @@
android:protectionLevel="signature|installer" />
<uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
- <!-- Allows notifications to be colorized
+ <!-- @SystemApi Allows notifications to be colorized
<p>Not for use by third-party applications. @hide -->
<permission android:name="android.permission.USE_COLORIZED_NOTIFICATIONS"
- android:protectionLevel="signature|setup" />
+ android:protectionLevel="signature|setup|role" />
<!-- Allows access to keyguard secure storage. Only allowed for system processes.
@hide -->
@@ -5103,6 +5345,12 @@
<permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows an application to manage weak escrow token on the device. This permission
+ is not available to third party applications.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_WEAK_ESCROW_TOKEN"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows an application to listen to trust changes. Only allowed for system processes.
@hide -->
<permission android:name="android.permission.TRUST_LISTENER"
@@ -5261,6 +5509,25 @@
android:description="@string/permdesc_startViewPermissionUsage"
android:protectionLevel="signature|installer" />
+ <!--
+ @SystemApi
+ Allows the holder to start the screen to review permission decisions.
+ <p>Protection level: signature|installer
+ @hide -->
+ <permission android:name="android.permission.START_REVIEW_PERMISSION_DECISIONS"
+ android:label="@string/permlab_startReviewPermissionDecisions"
+ android:description="@string/permdesc_startReviewPermissionDecisions"
+ android:protectionLevel="signature|installer" />
+
+ <!--
+ Allows the holder to start the screen with a list of app features.
+ <p>Protection level: signature|installer
+ -->
+ <permission android:name="android.permission.START_VIEW_APP_FEATURES"
+ android:label="@string/permlab_startViewAppFeatures"
+ android:description="@string/permdesc_startViewAppFeatures"
+ android:protectionLevel="signature|installer" />
+
<!-- Allows an application to query whether DO_NOT_ASK_CREDENTIALS_ON_BOOT
flag is set.
@hide -->
@@ -5282,7 +5549,7 @@
<!-- @SystemApi Allows access to MAC addresses of WiFi and Bluetooth peer devices.
@hide -->
<permission android:name="android.permission.PEERS_MAC_ADDRESS"
- android:protectionLevel="signature|setup" />
+ android:protectionLevel="signature|setup|role" />
<!-- Allows the Nfc stack to dispatch Nfc messages to applications. Applications
can use this permission to ensure incoming Nfc messages are from the Nfc stack
@@ -5469,6 +5736,11 @@
<permission android:name="android.permission.MANAGE_SMARTSPACE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to manage the cloudsearch service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_CLOUDSEARCH"
+ android:protectionLevel="signature" />
+
<!-- Allows an app to set the theme overlay in /vendor/overlay
being used.
@hide <p>Not for use by third-party applications.</p> -->
@@ -5581,11 +5853,11 @@
<permission android:name="android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS"
android:protectionLevel="signature" />
- <!-- Allows an app to mark a profile owner as managing an organization-owned device.
+ <!-- @SystemApi Allows an app to mark a profile owner as managing an organization-owned device.
<p>Not for use by third-party applications.
@hide -->
<permission android:name="android.permission.MARK_DEVICE_ORGANIZATION_OWNED"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|role" />
<!-- Allows financial apps to read filtered sms messages.
Protection level: signature|appop
@@ -5622,7 +5894,7 @@
<!-- @SystemApi Allows sensor privacy to be modified.
@hide -->
<permission android:name="android.permission.MANAGE_SENSOR_PRIVACY"
- android:protectionLevel="internal|role" />
+ android:protectionLevel="internal|role|installer" />
<!-- @SystemApi Allows sensor privacy changes to be observed.
@hide -->
@@ -5681,6 +5953,8 @@
<!-- Allows query of any normal app on the device, regardless of manifest declarations.
<p>Protection level: normal -->
<permission android:name="android.permission.QUERY_ALL_PACKAGES"
+ android:label="@string/permlab_queryAllPackages"
+ android:description="@string/permdesc_queryAllPackages"
android:protectionLevel="normal" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
@@ -5703,10 +5977,20 @@
<permission android:name="android.permission.ACCESS_TV_DESCRAMBLER"
android:protectionLevel="signature|privileged|vendorPrivileged" />
+ <!-- @SystemApi Allows an application to access shared filter of TV tuner HAL
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_TV_SHARED_FILTER"
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
+
<!-- Allows an application to create trusted displays. @hide -->
<permission android:name="android.permission.ADD_TRUSTED_DISPLAY"
android:protectionLevel="signature" />
+ <!-- Allows an application to create always-unlocked displays. @hide -->
+ <permission android:name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY"
+ android:protectionLevel="signature"/>
+
<!-- @hide @SystemApi Allows an application to access locusId events in the usage stats. -->
<permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
android:protectionLevel="signature|role" />
@@ -5770,11 +6054,10 @@
<permission android:name="android.permission.RENOUNCE_PERMISSIONS"
android:protectionLevel="signature|privileged" />
- <!-- Allows an application to read nearby streaming policy. The policy allows the device
- to stream its notifications and apps to nearby devices.
- @hide -->
+ <!-- Allows an application to read nearby streaming policy. The policy controls
+ whether to allow the device to stream its notifications and apps to nearby devices. -->
<permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="normal" />
<!-- @SystemApi Allows the holder to set the source of the data when setting a clip on the
clipboard.
@@ -5820,6 +6103,47 @@
<permission android:name="android.permission.READ_GLOBAL_APP_SEARCH_DATA"
android:protectionLevel="internal|role" />
+ <!-- @SystemApi Allows an application to create virtual devices in VirtualDeviceManager.
+ @hide -->
+ <permission android:name="android.permission.CREATE_VIRTUAL_DEVICE"
+ android:protectionLevel="internal|role" />
+
+ <!-- @SystemApi Must be required by a safety source to send an update using the
+ {@link android.safetycenter.SafetyCenterManager}.
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to launch device manager setup screens.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.LAUNCH_DEVICE_MANAGER_SETUP"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Allows an application to update certain device management related system
+ resources.
+ @hide -->
+ <permission android:name="android.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Allows an app to read whether SafetyCenter is enabled/disabled.
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.READ_SAFETY_CENTER_STATUS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Required to access the safety center internal APIs using the
+ {@link android.safetycenter.SafetyCenterManager}.
+ <p>Protection level: internal|installer|role
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_SAFETY_CENTER"
+ android:protectionLevel="internal|installer|role" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
@@ -5877,7 +6201,6 @@
android:excludeFromRecents="true"
android:documentLaunchMode="never"
android:relinquishTaskIdentity="true"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
android:process=":ui"
android:visibleToInstantApps="true">
<intent-filter>
@@ -5928,8 +6251,8 @@
android:process=":ui">
</activity>
<activity android:name="com.android.internal.app.PlatLogoActivity"
- android:theme="@style/Theme.DeviceDefault.DayNight"
- android:configChanges="orientation|keyboardHidden"
+ android:theme="@style/Theme.DeviceDefault.Wallpaper.NoTitleBar"
+ android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:icon="@drawable/platlogo"
android:process=":ui">
</activity>
@@ -6051,6 +6374,12 @@
android:process=":ui">
</activity>
+ <activity android:name="com.android.internal.app.LaunchAfterAuthenticationActivity"
+ android:theme="@style/Theme.Translucent.NoTitleBar"
+ android:excludeFromRecents="true"
+ android:process=":ui">
+ </activity>
+
<activity android:name="com.android.settings.notification.NotificationAccessConfirmationActivity"
android:theme="@style/Theme.Dialog.Confirmation"
android:excludeFromRecents="true">
@@ -6238,7 +6567,7 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
- <service android:name="com.android.server.pm.BackgroundDexOptService"
+ <service android:name="com.android.server.pm.BackgroundDexOptJobService"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index 35ed018..fbe7a44 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -22,12 +22,14 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import android.Manifest;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
+import android.os.Process;
import android.platform.test.annotations.AppModeFull;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -39,6 +41,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
@@ -88,6 +91,18 @@
InstrumentationRegistry.getInstrumentation().getTargetContext();
@Test
+ public void shellIsOnlySystemAppThatRequestsRevokePostNotificationsWithoutKill() {
+ List<PackageInfo> pkgs = sContext.getPackageManager().getInstalledPackages(
+ PackageManager.PackageInfoFlags.of(
+ PackageManager.GET_PERMISSIONS | PackageManager.MATCH_ALL));
+ int shellUid = Process.myUserHandle().getUid(Process.SHELL_UID);
+ for (PackageInfo pkg : pkgs) {
+ Assert.assertFalse(pkg.applicationInfo.uid != shellUid
+ && hasRevokeNotificationNoKillPermission(pkg));
+ }
+ }
+
+ @Test
public void platformPermissionPolicyIsUnaltered() throws Exception {
Map<String, PermissionInfo> declaredPermissionsMap =
getPermissionsForPackage(sContext, PLATFORM_PACKAGE_NAME);
@@ -236,6 +251,20 @@
assertWithMessage("list of offending permissions").that(offendingList).isEmpty();
}
+ private boolean hasRevokeNotificationNoKillPermission(PackageInfo info) {
+ if (info.requestedPermissions == null) {
+ return false;
+ }
+
+ for (int i = 0; i < info.requestedPermissions.length; i++) {
+ if (Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL.equals(
+ info.requestedPermissions[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private List<ExpectedPermissionInfo> loadExpectedPermissions(int resourceId) throws Exception {
List<ExpectedPermissionInfo> permissions = new ArrayList<>();
try (InputStream in = sContext.getResources().openRawResource(resourceId)) {
@@ -383,9 +412,6 @@
case "incidentReportApprover": {
protectionLevel |= PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER;
} break;
- case "documenter": {
- protectionLevel |= PermissionInfo.PROTECTION_FLAG_DOCUMENTER;
- } break;
case "appPredictor": {
protectionLevel |= PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR;
} break;
diff --git a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
index dba2522..a1eca49 100644
--- a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
+++ b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
@@ -31,6 +31,7 @@
import android.Manifest.permission.GET_ACCOUNTS
import android.Manifest.permission.NEARBY_WIFI_DEVICES
import android.Manifest.permission.PACKAGE_USAGE_STATS
+import android.Manifest.permission.POST_NOTIFICATIONS
import android.Manifest.permission.PROCESS_OUTGOING_CALLS
import android.Manifest.permission.READ_CALENDAR
import android.Manifest.permission.READ_CALL_LOG
@@ -157,6 +158,7 @@
// Add runtime permissions added in T which were _not_ split from a previously existing
// runtime permission
+ expectedPerms.add(POST_NOTIFICATIONS)
expectedPerms.add(NEARBY_WIFI_DEVICES)
assertThat(expectedPerms).containsExactlyElementsIn(platformRuntimePerms.map { it.name })
diff --git a/tests/tests/permission3/Android.bp b/tests/tests/permission3/Android.bp
index 3d91b81..75ef5b3 100644
--- a/tests/tests/permission3/Android.bp
+++ b/tests/tests/permission3/Android.bp
@@ -47,11 +47,17 @@
":CtsUsePermissionApp30",
":CtsUsePermissionApp30WithBackground",
":CtsUsePermissionApp30WithBluetooth",
+ ":CtsUsePermissionApp31",
+ ":CtsUsePermissionApp32",
":CtsUsePermissionAppLatest",
":CtsUsePermissionAppLatestNone",
":CtsUsePermissionAppWithOverlay",
":CtsAccessMicrophoneApp",
":CtsAccessMicrophoneApp2",
+ ":CtsAccessMicrophoneAppLocationProvider",
+ ":CtsHelperAppOverlay",
+ ":CtsCreateNotificationChannelsApp31",
+ ":CtsCreateNotificationChannelsApp33",
],
test_suites: [
"cts",
diff --git a/tests/tests/permission3/AndroidTest.xml b/tests/tests/permission3/AndroidTest.xml
index d708d76..129cb4d 100644
--- a/tests/tests/permission3/AndroidTest.xml
+++ b/tests/tests/permission3/AndroidTest.xml
@@ -21,6 +21,7 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
@@ -41,6 +42,7 @@
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="push" value="CtsAccessMicrophoneApp.apk->/data/local/tmp/cts/permission3/CtsAccessMicrophoneApp.apk" />
<option name="push" value="CtsAccessMicrophoneApp2.apk->/data/local/tmp/cts/permission3/CtsAccessMicrophoneApp2.apk" />
+ <option name="push" value="CtsAccessMicrophoneAppLocationProvider.apk->/data/local/tmp/cts/permission3/CtsAccessMicrophoneAppLocationProvider.apk" />
<option name="push" value="CtsPermissionPolicyApp25.apk->/data/local/tmp/cts/permission3/CtsPermissionPolicyApp25.apk" />
<option name="push" value="CtsUsePermissionApp22.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp22.apk" />
<option name="push" value="CtsUsePermissionApp22CalendarOnly.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp22CalendarOnly.apk" />
@@ -53,9 +55,14 @@
<option name="push" value="CtsUsePermissionApp30.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30.apk" />
<option name="push" value="CtsUsePermissionApp30WithBackground.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30WithBackground.apk" />
<option name="push" value="CtsUsePermissionApp30WithBluetooth.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30WithBluetooth.apk" />
+ <option name="push" value="CtsUsePermissionApp31.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp31.apk" />
+ <option name="push" value="CtsUsePermissionApp32.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp32.apk" />
<option name="push" value="CtsUsePermissionAppLatest.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatest.apk" />
<option name="push" value="CtsUsePermissionAppLatestNone.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatestNone.apk" />
<option name="push" value="CtsUsePermissionAppWithOverlay.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppWithOverlay.apk" />
+ <option name="push" value="CtsHelperAppOverlay.apk->/data/local/tmp/cts/permission3/CtsHelperAppOverlay.apk" />
+ <option name="push" value="CtsCreateNotificationChannelsApp31.apk->/data/local/tmp/cts/permission3/CtsCreateNotificationChannelsApp31.apk" />
+ <option name="push" value="CtsCreateNotificationChannelsApp33.apk->/data/local/tmp/cts/permission3/CtsCreateNotificationChannelsApp33.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/permission3/CreateNotificationChannelsApp31/Android.bp b/tests/tests/permission3/CreateNotificationChannelsApp31/Android.bp
new file mode 100644
index 0000000..265a01c
--- /dev/null
+++ b/tests/tests/permission3/CreateNotificationChannelsApp31/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsCreateNotificationChannelsApp31",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "31",
+
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+}
diff --git a/tests/tests/permission3/CreateNotificationChannelsApp31/AndroidManifest.xml b/tests/tests/permission3/CreateNotificationChannelsApp31/AndroidManifest.xml
new file mode 100644
index 0000000..d3d73a0
--- /dev/null
+++ b/tests/tests/permission3/CreateNotificationChannelsApp31/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.permission3.cts.usepermission"
+ android:versionCode="1">
+
+ <uses-sdk android:minSdkVersion="31" android:targetSdkVersion="31" />
+
+ <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+
+ <application android:label="CreateNotificationChannels">
+ <activity android:name=".CreateNotificationChannelsActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="usepermission.createchannels.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/tests/permission3/CreateNotificationChannelsApp31/src/android/permission3/cts/usepermission/CreateNotificationChannelsActivity.kt b/tests/tests/permission3/CreateNotificationChannelsApp31/src/android/permission3/cts/usepermission/CreateNotificationChannelsActivity.kt
new file mode 100644
index 0000000..9e09ad1
--- /dev/null
+++ b/tests/tests/permission3/CreateNotificationChannelsApp31/src/android/permission3/cts/usepermission/CreateNotificationChannelsActivity.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts.usepermission
+
+import android.Manifest
+import android.app.Activity
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Handler
+import android.os.Looper
+
+const val EXTRA_DELETE_CHANNELS_ON_CLOSE = "extra_delete_channels_on_close"
+const val EXTRA_CREATE_CHANNELS = "extra_create"
+const val EXTRA_CREATE_CHANNELS_DELAYED = "extra_create_delayed"
+const val EXTRA_REQUEST_PERMISSIONS = "extra_request_permissions"
+const val EXTRA_REQUEST_PERMISSIONS_DELAYED = "extra_request_permissions_delayed"
+const val CHANNEL_ID = "channel_id"
+const val BROADCAST_ACTION = "usepermission.createchannels.BROADCAST"
+const val DELAY_MS = 1000L
+
+class CreateNotificationChannelsActivity : Activity() {
+ lateinit var notificationManager: NotificationManager
+ override fun onStart() {
+ val handler = Handler(Looper.getMainLooper())
+ notificationManager = baseContext.getSystemService(NotificationManager::class.java)!!
+ if (intent.getBooleanExtra(EXTRA_CREATE_CHANNELS, false)) {
+ if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
+ notificationManager.createNotificationChannel(NotificationChannel(CHANNEL_ID,
+ "Foreground Services", NotificationManager.IMPORTANCE_HIGH))
+ }
+ } else if (intent.getBooleanExtra(EXTRA_CREATE_CHANNELS_DELAYED, false)) {
+ handler.postDelayed({
+ if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
+ notificationManager.createNotificationChannel(NotificationChannel(CHANNEL_ID,
+ "Foreground Services", NotificationManager.IMPORTANCE_HIGH))
+ }
+ }, DELAY_MS)
+ }
+
+ if (intent.getBooleanExtra(EXTRA_REQUEST_PERMISSIONS, false)) {
+ requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), 0)
+ } else if (intent.getBooleanExtra(EXTRA_REQUEST_PERMISSIONS_DELAYED, false)) {
+ handler.postDelayed({
+ requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), 0)
+ }, DELAY_MS)
+ }
+ super.onStart()
+ }
+
+ override fun onPause() {
+ if (intent.getBooleanExtra(EXTRA_DELETE_CHANNELS_ON_CLOSE, false)) {
+ notificationManager.deleteNotificationChannel(CHANNEL_ID)
+ }
+ super.onPause()
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array<out String>,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ val grantedPerms = arrayListOf<String>()
+ for ((i, permName) in permissions.withIndex()) {
+ if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
+ grantedPerms.add(permName)
+ }
+ }
+ sendBroadcast(
+ Intent(BROADCAST_ACTION).putStringArrayListExtra(
+ PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, grantedPerms))
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/CreateNotificationChannelsApp33/Android.bp b/tests/tests/permission3/CreateNotificationChannelsApp33/Android.bp
new file mode 100644
index 0000000..67bf182
--- /dev/null
+++ b/tests/tests/permission3/CreateNotificationChannelsApp33/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsCreateNotificationChannelsApp33",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "33",
+
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+}
diff --git a/tests/tests/permission3/CreateNotificationChannelsApp33/AndroidManifest.xml b/tests/tests/permission3/CreateNotificationChannelsApp33/AndroidManifest.xml
new file mode 100644
index 0000000..c302116
--- /dev/null
+++ b/tests/tests/permission3/CreateNotificationChannelsApp33/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.permission3.cts.usepermission"
+ android:versionCode="1">
+
+ <uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
+
+ <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+
+ <application android:label="CreateNotificationChannels">
+ <activity android:name=".CreateNotificationChannelsActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="usepermission.createchannels.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/tests/permission3/CreateNotificationChannelsApp33/src/android/permission3/cts/usepermission/CreateNotificationChannelsActivity.kt b/tests/tests/permission3/CreateNotificationChannelsApp33/src/android/permission3/cts/usepermission/CreateNotificationChannelsActivity.kt
new file mode 100644
index 0000000..e9550d1
--- /dev/null
+++ b/tests/tests/permission3/CreateNotificationChannelsApp33/src/android/permission3/cts/usepermission/CreateNotificationChannelsActivity.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts.usepermission
+
+import android.Manifest
+import android.app.Activity
+import android.app.NotificationChannel
+import android.app.NotificationManager
+
+const val EXTRA_DELETE_CHANNELS_ON_CLOSE = "extra_delete_channels_on_close"
+const val EXTRA_CREATE_CHANNELS = "extra_create"
+const val EXTRA_REQUEST_PERMISSIONS = "extra_request_permissions"
+const val EXTRA_REQUEST_PERMISSIONS_DELAYED = "extra_request_permissions_delayed"
+const val CHANNEL_ID = "channel_id"
+const val DELAY_MS = 1000L
+
+class CreateNotificationChannelsActivity : Activity() {
+ lateinit var notificationManager: NotificationManager
+ override fun onStart() {
+ notificationManager = baseContext.getSystemService(NotificationManager::class.java)!!
+ if (intent.getBooleanExtra(EXTRA_CREATE_CHANNELS, false)) {
+ if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
+ notificationManager.createNotificationChannel(NotificationChannel(CHANNEL_ID,
+ "Foreground Services", NotificationManager.IMPORTANCE_HIGH))
+ }
+ }
+
+ if (intent.getBooleanExtra(EXTRA_REQUEST_PERMISSIONS, false)) {
+ requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), 0)
+ } else if (intent.getBooleanExtra(EXTRA_REQUEST_PERMISSIONS_DELAYED, false)) {
+ mainThreadHandler.postDelayed({
+ requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), 0)
+ }, DELAY_MS)
+ }
+
+ super.onStart()
+ }
+
+ override fun onPause() {
+ if (intent.getBooleanExtra(EXTRA_DELETE_CHANNELS_ON_CLOSE, false)) {
+ notificationManager.deleteNotificationChannel(CHANNEL_ID)
+ }
+ super.onPause()
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/HelperAppOverlay/Android.bp b/tests/tests/permission3/HelperAppOverlay/Android.bp
new file mode 100644
index 0000000..94d9653
--- /dev/null
+++ b/tests/tests/permission3/HelperAppOverlay/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsHelperAppOverlay",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "30",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+ certificate: ":cts-testkey2",
+}
diff --git a/tests/tests/permission3/HelperAppOverlay/AndroidManifest.xml b/tests/tests/permission3/HelperAppOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..04d5a4b
--- /dev/null
+++ b/tests/tests/permission3/HelperAppOverlay/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.permission3.cts.helper.overlay">
+
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+ <application>
+ <activity android:name=".OverlayActivity" android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/tests/permission3/HelperAppOverlay/src/android/permission3/cts/helper/overlay/OverlayActivity.kt b/tests/tests/permission3/HelperAppOverlay/src/android/permission3/cts/helper/overlay/OverlayActivity.kt
new file mode 100644
index 0000000..2c1497f
--- /dev/null
+++ b/tests/tests/permission3/HelperAppOverlay/src/android/permission3/cts/helper/overlay/OverlayActivity.kt
@@ -0,0 +1,26 @@
+package android.permission3.cts.helper.overlay
+
+import android.app.Activity
+import android.os.Bundle
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.WindowManager
+import android.widget.LinearLayout
+import android.widget.TextView
+
+class OverlayActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val mainLayout = LinearLayout(this)
+ mainLayout.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+ val textView = TextView(this)
+
+ textView.text = "Find me!"
+ mainLayout.addView(textView)
+
+ val windowParams = WindowManager.LayoutParams()
+ windowParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+ windowManager.addView(mainLayout, windowParams)
+ }
+}
diff --git a/tests/tests/permission3/UsePermissionApp31/Android.bp b/tests/tests/permission3/UsePermissionApp31/Android.bp
new file mode 100644
index 0000000..48a2d4f
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp31/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsUsePermissionApp31",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+ certificate: ":cts-testkey2",
+ target_sdk_version: "31",
+ min_sdk_version: "31",
+}
diff --git a/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml b/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml
new file mode 100644
index 0000000..dfa12b7
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.permission3.cts.usepermission">
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.BODY_SENSORS" />
+ <application>
+ <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+ <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionApp32/Android.bp b/tests/tests/permission3/UsePermissionApp32/Android.bp
new file mode 100644
index 0000000..adf24cf
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp32/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsUsePermissionApp32",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+ certificate: ":cts-testkey2",
+
+ min_sdk_version: "32",
+ target_sdk_version: "32",
+}
diff --git a/tests/tests/permission3/UsePermissionApp32/AndroidManifest.xml b/tests/tests/permission3/UsePermissionApp32/AndroidManifest.xml
new file mode 100644
index 0000000..35df6c8
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp32/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.permission3.cts.usepermission">
+ <uses-permission android:name="android.permission.BODY_SENSORS" />
+ <application>
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionAppLatest/AndroidManifest.xml b/tests/tests/permission3/UsePermissionAppLatest/AndroidManifest.xml
index 1d8cdf3..0c125d0 100644
--- a/tests/tests/permission3/UsePermissionAppLatest/AndroidManifest.xml
+++ b/tests/tests/permission3/UsePermissionAppLatest/AndroidManifest.xml
@@ -25,7 +25,7 @@
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-
+ <uses-permission android:name="android.permission.BODY_SENSORS" />
<application>
<activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
<activity android:name=".FinishOnCreateActivity" android:exported="true" />
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/Android.bp b/tests/tests/permission3/UsePermissionAppLocationProvider/Android.bp
new file mode 100644
index 0000000..a2cd88d
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAccessMicrophoneAppLocationProvider",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "31",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/AndroidManifest.xml b/tests/tests/permission3/UsePermissionAppLocationProvider/AndroidManifest.xml
new file mode 100644
index 0000000..56afc09
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.permission3.cts.accessmicrophoneapplocationprovider">
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+ <attribution
+ android:label="@string/attribution_label"
+ android:tag="test.tag" />
+
+ <application
+ android:attributionsAreUserVisible="true"
+ android:label="LocationProviderWithMicApp">
+ <activity android:name=".AddLocationProviderActivity" android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".UseMicrophoneActivity" android:exported="true"/>
+ </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/res/values/strings.xml b/tests/tests/permission3/UsePermissionAppLocationProvider/res/values/strings.xml
new file mode 100644
index 0000000..9682d7f
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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>
+ <string name="attribution_label">Attribution Label</string>
+</resources>
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt b/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt
new file mode 100644
index 0000000..3b3355c
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.permission3.cts.accessmicrophoneapplocationprovider
+
+import android.app.Activity
+import android.location.Criteria
+import android.location.LocationManager
+import android.os.Bundle
+
+/**
+ * An activity that adds this package as a test location provider and uses microphone.
+ */
+class AddLocationProviderActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val attrContext = createAttributionContext("test.tag")
+ val locationManager = attrContext.getSystemService(LocationManager::class.java)
+ locationManager.addTestProvider(
+ packageName, false, false, false, false, false, false, false, Criteria.POWER_LOW,
+ Criteria.ACCURACY_COARSE
+ )
+
+ setResult(RESULT_OK)
+ finish()
+ }
+}
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt b/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt
new file mode 100644
index 0000000..0d8374f
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.permission3.cts.accessmicrophoneapplocationprovider
+
+import android.app.Activity
+import android.content.Context
+import android.media.AudioFormat
+import android.media.AudioRecord
+import android.media.MediaRecorder
+import android.os.Bundle
+import android.os.Handler
+
+private const val USE_DURATION_MS = 10000L
+private const val SAMPLE_RATE_HZ = 44100
+
+/**
+ * An activity that uses microphone.
+ */
+class UseMicrophoneActivity : Activity() {
+ private var recorder: AudioRecord? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val attrContext = createAttributionContext("test.tag")
+ useMic(attrContext)
+ setResult(RESULT_OK)
+ finish()
+ }
+
+ override fun finish() {
+ recorder?.stop()
+ recorder = null
+ super.finish()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ finish()
+ }
+
+ private fun useMic(context: Context) {
+ recorder = AudioRecord.Builder()
+ .setAudioSource(MediaRecorder.AudioSource.MIC)
+ .setAudioFormat(AudioFormat.Builder()
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setSampleRate(SAMPLE_RATE_HZ)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+ .build())
+ .setContext(context)
+ .build()
+ recorder?.startRecording()
+ Handler().postDelayed({ finish() }, USE_DURATION_MS)
+ }
+}
diff --git a/tests/tests/permission3/UsePermissionAppWithOverlay/src/android/permission3/cts/usepermission/OverlayActivity.kt b/tests/tests/permission3/UsePermissionAppWithOverlay/src/android/permission3/cts/usepermission/OverlayActivity.kt
index 2f0b9fc..6dbd472 100644
--- a/tests/tests/permission3/UsePermissionAppWithOverlay/src/android/permission3/cts/usepermission/OverlayActivity.kt
+++ b/tests/tests/permission3/UsePermissionAppWithOverlay/src/android/permission3/cts/usepermission/OverlayActivity.kt
@@ -18,14 +18,15 @@
params.flags = (WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
- WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
if (!intent.getBooleanExtra(EXTRA_FULL_OVERLAY, true)) {
params.gravity = Gravity.LEFT or Gravity.TOP
- val left = intent.getIntExtra(DIALOG_LEFT, params.x)
- val top = intent.getIntExtra(DIALOG_TOP, params.y)
- val right = intent.getIntExtra(DIALOG_RIGHT, params.x + params.width)
- val bottom = intent.getIntExtra(MESSAGE_BOTTOM, top + 1)
+ val left = intent.getIntExtra(OVERLAY_LEFT, params.x)
+ val top = intent.getIntExtra(OVERLAY_TOP, params.y)
+ val right = intent.getIntExtra(OVERLAY_RIGHT, params.x + params.width)
+ val bottom = intent.getIntExtra(OVERLAY_BOTTOM, top + 1)
params.x = left
params.y = top
params.width = right - left
@@ -46,9 +47,9 @@
companion object {
const val EXTRA_FULL_OVERLAY = "android.permission3.cts.usepermission.extra.FULL_OVERLAY"
- const val DIALOG_LEFT = "android.permission3.cts.usepermission.extra.DIALOG_LEFT"
- const val DIALOG_TOP = "android.permission3.cts.usepermission.extra.DIALOG_TOP"
- const val DIALOG_RIGHT = "android.permission3.cts.usepermission.extra.DIALOG_RIGHT"
- const val MESSAGE_BOTTOM = "android.permission3.cts.usepermission.extra.MESSAGE_BOTTOM"
+ const val OVERLAY_LEFT = "android.permission3.cts.usepermission.extra.OVERLAY_LEFT"
+ const val OVERLAY_TOP = "android.permission3.cts.usepermission.extra.OVERLAY_TOP"
+ const val OVERLAY_RIGHT = "android.permission3.cts.usepermission.extra.OVERLAY_RIGHT"
+ const val OVERLAY_BOTTOM = "android.permission3.cts.usepermission.extra.OVERLAY_BOTTOM"
}
-}
\ No newline at end of file
+}
diff --git a/tests/tests/permission3/src/android/permission3/cts/BasePermissionHubTest.kt b/tests/tests/permission3/src/android/permission3/cts/BasePermissionHubTest.kt
new file mode 100644
index 0000000..014a4f3
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/BasePermissionHubTest.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts
+
+import android.content.Intent
+import com.android.compatibility.common.util.SystemUtil
+
+/** Base class for the permission hub tests. */
+abstract class BasePermissionHubTest : BasePermissionTest() {
+
+ protected fun openMicrophoneTimeline() {
+ SystemUtil.runWithShellPermissionIdentity {
+ context.startActivity(Intent(Intent.ACTION_REVIEW_PERMISSION_HISTORY).apply {
+ putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME,
+ android.Manifest.permission_group.MICROPHONE)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ })
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
index c0ee17b..3968fc3 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
@@ -27,6 +27,7 @@
import android.support.test.uiautomator.BySelector
import android.support.test.uiautomator.UiDevice
import android.support.test.uiautomator.UiObject2
+import android.text.Html
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import com.android.compatibility.common.util.DisableAnimationRule
@@ -59,6 +60,10 @@
private val mPermissionControllerResources: Resources = context.createPackageContext(
context.packageManager.permissionControllerPackageName, 0).resources
+ protected val isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+ protected val isWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
+ protected val isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+
@get:Rule
val disableAnimationRule = DisableAnimationRule()
@@ -99,11 +104,14 @@
pressHome()
}
- protected fun getPermissionControllerString(res: String): Pattern =
- Pattern.compile(Pattern.quote(mPermissionControllerResources.getString(
- mPermissionControllerResources.getIdentifier(
- res, "string", "com.android.permissioncontroller"))),
- Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE)
+ protected fun getPermissionControllerString(res: String, vararg formatArgs: Any): Pattern {
+ val textWithHtml = mPermissionControllerResources.getString(
+ mPermissionControllerResources.getIdentifier(
+ res, "string", "com.android.permissioncontroller"), *formatArgs)
+ val textWithoutHtml = Html.fromHtml(textWithHtml, 0).toString()
+ return Pattern.compile(Pattern.quote(textWithoutHtml),
+ Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE)
+ }
protected fun installPackage(
apkPath: String,
diff --git a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
index 24ab5aa..8b63fb3 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
@@ -28,10 +28,8 @@
import android.support.test.uiautomator.BySelector
import android.support.test.uiautomator.UiScrollable
import android.support.test.uiautomator.UiSelector
-import android.support.test.uiautomator.StaleObjectException
import android.text.Spanned
import android.text.style.ClickableSpan
-import android.util.Log
import android.view.View
import com.android.compatibility.common.util.SystemUtil.eventually
import org.junit.After
@@ -54,6 +52,9 @@
const val APP_APK_PATH_28 = "$APK_DIRECTORY/CtsUsePermissionApp28.apk"
const val APP_APK_PATH_29 = "$APK_DIRECTORY/CtsUsePermissionApp29.apk"
const val APP_APK_PATH_30 = "$APK_DIRECTORY/CtsUsePermissionApp30.apk"
+ const val APP_APK_PATH_31 = "$APK_DIRECTORY/CtsUsePermissionApp31.apk"
+ const val APP_APK_PATH_32 = "$APK_DIRECTORY/CtsUsePermissionApp32.apk"
+
const val APP_APK_PATH_30_WITH_BACKGROUND =
"$APK_DIRECTORY/CtsUsePermissionApp30WithBackground.apk"
const val APP_APK_PATH_30_WITH_BLUETOOTH =
@@ -61,6 +62,10 @@
const val APP_APK_PATH_LATEST = "$APK_DIRECTORY/CtsUsePermissionAppLatest.apk"
const val APP_APK_PATH_LATEST_NONE = "$APK_DIRECTORY/CtsUsePermissionAppLatestNone.apk"
const val APP_APK_PATH_WITH_OVERLAY = "$APK_DIRECTORY/CtsUsePermissionAppWithOverlay.apk"
+ const val APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31 =
+ "$APK_DIRECTORY/CtsCreateNotificationChannelsApp31.apk"
+ const val APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_33 =
+ "$APK_DIRECTORY/CtsCreateNotificationChannelsApp33.apk"
const val APP_PACKAGE_NAME = "android.permission3.cts.usepermission"
const val ALLOW_BUTTON =
@@ -76,6 +81,8 @@
"com.android.permissioncontroller:" +
"id/permission_no_upgrade_and_dont_ask_again_button"
+ const val ALLOW_ALWAYS_RADIO_BUTTON =
+ "com.android.permissioncontroller:id/allow_always_radio_button"
const val ALLOW_RADIO_BUTTON = "com.android.permissioncontroller:id/allow_radio_button"
const val ALLOW_FOREGROUND_RADIO_BUTTON =
"com.android.permissioncontroller:id/allow_foreground_only_radio_button"
@@ -91,6 +98,8 @@
const val DENY_AND_DONT_ASK_AGAIN_BUTTON_TEXT =
"grant_dialog_button_deny_and_dont_ask_again"
const val NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON_TEXT = "grant_dialog_button_no_upgrade"
+
+ const val REQUEST_LOCATION_MESSAGE = "permgrouprequest_location"
}
enum class PermissionState {
@@ -99,10 +108,6 @@
DENIED_WITH_PREJUDICE
}
- protected val isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
- protected val isWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
- protected val isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
-
private val platformResources = context.createPackageContext("android", 0).resources
private val permissionToLabelResNameMap = mapOf(
// Contacts
@@ -132,6 +137,8 @@
to "@android:string/permgrouplab_location",
android.Manifest.permission.ACCESS_COARSE_LOCATION
to "@android:string/permgrouplab_location",
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
+ to "@android:string/permgrouplab_location",
// Phone
android.Manifest.permission.READ_PHONE_STATE
to "@android:string/permgrouplab_phone",
@@ -151,6 +158,8 @@
android.Manifest.permission.CAMERA to "@android:string/permgrouplab_camera",
// Body sensors
android.Manifest.permission.BODY_SENSORS to "@android:string/permgrouplab_sensors",
+ android.Manifest.permission.BODY_SENSORS_BACKGROUND
+ to "@android:string/permgrouplab_sensors",
// Bluetooth
android.Manifest.permission.BLUETOOTH_CONNECT to
"@android:string/permgrouplab_nearby_devices",
@@ -475,7 +484,7 @@
// Automotive doesn't support one time permissions, and thus
// won't show an "Ask every time" message
when (state) {
- PermissionState.ALLOWED ->By.text(
+ PermissionState.ALLOWED -> By.text(
getPermissionControllerString("app_permission_button_allow"))
PermissionState.DENIED -> By.text(
getPermissionControllerString("app_permission_button_deny"))
@@ -505,6 +514,8 @@
PermissionState.ALLOWED ->
if (showsForegroundOnlyButton(permission)) {
By.res(ALLOW_FOREGROUND_RADIO_BUTTON)
+ } else if (showsAlwaysButton(permission)) {
+ By.res(ALLOW_ALWAYS_RADIO_BUTTON)
} else if (isMediaStorageButton(permission, targetSdk)) {
// Uses "allow_foreground_only_radio_button" as id
byTextRes(R.string.allow_media_storage)
@@ -581,6 +592,12 @@
else -> false
}
+ private fun showsAlwaysButton(permission: String): Boolean =
+ when (permission) {
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION -> true
+ else -> false
+ }
+
private fun isMediaStorageButton(permission: String, targetSdk: Int): Boolean =
if (isTv || isWatch) {
false
diff --git a/tests/tests/permission3/src/android/permission3/cts/NoPermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/NoPermissionTest.kt
index 1d8ed8e..e04b37b 100644
--- a/tests/tests/permission3/src/android/permission3/cts/NoPermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/NoPermissionTest.kt
@@ -18,6 +18,8 @@
import android.app.Activity
import androidx.test.runner.AndroidJUnit4
+import com.android.modules.utils.build.SdkLevel
+import org.junit.Assume
import org.junit.Test
import org.junit.runner.RunWith
@@ -25,6 +27,7 @@
class NoPermissionTest : BaseUsePermissionTest() {
@Test
fun testStartActivity22() {
+ Assume.assumeFalse(SdkLevel.isAtLeastT())
installPackage(APP_APK_PATH_22_NONE)
startAppActivityAndAssertResultCode(Activity.RESULT_OK) {}
diff --git a/tests/tests/permission3/src/android/permission3/cts/NotificationPermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/NotificationPermissionTest.kt
new file mode 100644
index 0000000..c72b97d
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/NotificationPermissionTest.kt
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts
+
+import android.Manifest.permission.POST_NOTIFICATIONS
+import android.Manifest.permission.RECORD_AUDIO
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+import android.content.pm.PackageManager.PERMISSION_GRANTED
+import android.os.Process
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import org.junit.After
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import java.util.concurrent.CountDownLatch
+
+const val EXTRA_DELETE_CHANNELS_ON_CLOSE = "extra_delete_channels_on_close"
+const val EXTRA_CREATE_CHANNELS = "extra_create"
+const val EXTRA_CREATE_CHANNELS_DELAYED = "extra_create_delayed"
+const val EXTRA_REQUEST_PERMISSIONS = "extra_request_permissions"
+const val EXTRA_REQUEST_PERMISSIONS_DELAYED = "extra_request_permissions_delayed"
+const val ACTIVITY_NAME = "CreateNotificationChannelsActivity"
+const val INTENT_ACTION = "usepermission.createchannels.MAIN"
+const val BROADCAST_ACTION = "usepermission.createchannels.BROADCAST"
+const val NOTIFICATION_PERMISSION_ENABLED = "notification_permission_enabled"
+const val DELAY_MS = 2000L
+
+class NotificationPermissionTest : BaseUsePermissionTest() {
+
+ private val cr = context.createContextAsUser(UserHandle.SYSTEM, 0).contentResolver
+ private var previousEnableState = 0
+ private var countDown: CountDownLatch = CountDownLatch(1)
+ private var allowedGroups = listOf<String>()
+ private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ countDown.countDown()
+ allowedGroups = intent?.getStringArrayListExtra(
+ PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS) ?: emptyList()
+ }
+ }
+
+ @Before
+ fun setLatchAndEnablePermission() {
+ runWithShellPermissionIdentity {
+ previousEnableState = Settings.Secure.getInt(cr, NOTIFICATION_PERMISSION_ENABLED, 0)
+ Settings.Secure.putInt(cr, NOTIFICATION_PERMISSION_ENABLED, 1)
+ }
+ countDown = CountDownLatch(1)
+ allowedGroups = listOf()
+ context.registerReceiver(receiver, IntentFilter(BROADCAST_ACTION))
+ }
+
+ @After
+ fun resetPermissionAndRemoveReceiver() {
+ runWithShellPermissionIdentity {
+ Settings.Secure.putInt(cr, NOTIFICATION_PERMISSION_ENABLED, previousEnableState)
+ }
+ context.unregisterReceiver(receiver)
+ }
+
+ @Test
+ fun notificationPermissionAddedForLegacyApp() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ runWithShellPermissionIdentity {
+ Assert.assertTrue("SDK < 32 apps should have POST_NOTIFICATIONS added implicitly",
+ context.packageManager.getPackageInfo(APP_PACKAGE_NAME,
+ PackageManager.GET_PERMISSIONS).requestedPermissions
+ .contains(POST_NOTIFICATIONS))
+ }
+ }
+
+ // TODO ntmyren: enable when SDK 33 is a valid target
+ @Test
+ @Ignore
+ fun notificationPermissionIsNotImplicitlyAddedTo33Apps() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_33, expectSuccess = true)
+ runWithShellPermissionIdentity {
+ Assert.assertFalse("SDK < 33 apps should NOT have POST_NOTIFICATIONS added implicitly",
+ context.packageManager.getPackageInfo(APP_PACKAGE_NAME,
+ PackageManager.GET_PERMISSIONS).requestedPermissions
+ .contains(POST_NOTIFICATIONS))
+ }
+ }
+
+ // TODO ntmyren: enable when SDK 33 is a valid target
+ @Test
+ @Ignore
+ fun reviewRequiredClearedForTAppsOnLaunch() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_33, expectSuccess = true)
+ runWithShellPermissionIdentity {
+ context.packageManager.updatePermissionFlags(POST_NOTIFICATIONS, APP_PACKAGE_NAME,
+ FLAG_PERMISSION_REVIEW_REQUIRED, FLAG_PERMISSION_REVIEW_REQUIRED,
+ Process.myUserHandle())
+ }
+ launchApp()
+ waitForIdle()
+ assertNotificationReviewRequiredCleared()
+ }
+
+ @Test
+ fun notificationPromptShowsForLegacyAppAfterCreatingNotificationChannels() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp()
+ waitForIdle()
+ clickPermissionRequestAllowButton()
+ }
+
+ @Test
+ fun notificationPromptShowsForLegacyAppWithNotificationChannelsOnStart() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp()
+ waitForIdle()
+ pressBack()
+ pressBack()
+ waitForIdle()
+ launchApp()
+ clickPermissionRequestAllowButton()
+ }
+
+ @Test
+ fun notificationPromptDoesNotShowForLegacyAppWithNoNotificationChannels() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp(createChannels = false)
+ try {
+ clickPermissionRequestAllowButton()
+ Assert.fail("Expected not to find permission request dialog")
+ } catch (expected: RuntimeException) {
+ // Do nothing
+ }
+ }
+
+ @Test
+ fun notificationGrantedAndReviewRequiredClearedOnLegacyGrant() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp()
+ clickPermissionRequestAllowButton()
+ assertAppPermissionGrantedState(POST_NOTIFICATIONS, granted = true)
+ assertNotificationReviewRequiredCleared()
+ }
+
+ @Test
+ fun notificationReviewRequiredClearedOnLegacyDeny() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp()
+ clickPermissionRequestDenyButton()
+ assertNotificationReviewRequiredCleared()
+ }
+
+ @Test
+ fun ensureNonSystemServerPackageCannotShowPromptForOtherPackage() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ runWithShellPermissionIdentity {
+ val grantPermission = Intent(PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER)
+ grantPermission.putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME)
+ grantPermission.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES,
+ arrayOf(POST_NOTIFICATIONS))
+ grantPermission.setPackage(context.packageManager.permissionControllerPackageName)
+ grantPermission.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(grantPermission)
+ }
+ try {
+ clickPermissionRequestAllowButton()
+ Assert.fail("Expected not to find permission request dialog")
+ } catch (expected: RuntimeException) {
+ // Do nothing
+ }
+ }
+
+ @Test
+ fun mergeAppPermissionRequestIntoNotificationAndVerifyResult() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp(requestPermissionsDelayed = true)
+ Thread.sleep(DELAY_MS)
+ clickPermissionRequestAllowButton()
+ assertAppPermissionGrantedState(POST_NOTIFICATIONS, granted = true)
+ clickPermissionRequestAllowForegroundButton()
+ assertAppPermissionGrantedState(RECORD_AUDIO, granted = true)
+ countDown.await()
+ // Result should contain only the microphone request
+ Assert.assertEquals(listOf(RECORD_AUDIO), allowedGroups)
+ }
+
+ @Test
+ fun mergeNotificationRequestIntoAppPermissionRequestAndVerifyResult() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp(createChannels = false, createChannelsDelayed = true, requestPermissions = true)
+ Thread.sleep(DELAY_MS)
+ clickPermissionRequestAllowForegroundButton()
+ assertAppPermissionGrantedState(RECORD_AUDIO, granted = true)
+ clickPermissionRequestAllowButton()
+ assertAppPermissionGrantedState(POST_NOTIFICATIONS, granted = true)
+ countDown.await()
+ // Result should contain only the microphone request
+ Assert.assertEquals(listOf(RECORD_AUDIO), allowedGroups)
+ }
+
+ private fun assertAppPermissionGrantedState(permission: String, granted: Boolean) {
+ runWithShellPermissionIdentity {
+ Assert.assertEquals("Expected $permission to be granted", context.packageManager
+ .checkPermission(permission, APP_PACKAGE_NAME), PERMISSION_GRANTED)
+ }
+ }
+
+ private fun assertNotificationReviewRequiredCleared() {
+ runWithShellPermissionIdentity {
+ val permFlags = context.packageManager
+ .getPermissionFlags(POST_NOTIFICATIONS, APP_PACKAGE_NAME, Process.myUserHandle())
+ Assert.assertEquals("Expected REVIEW_REQUIRED to bel cleared for POST_NOTIFICATIONS",
+ permFlags and FLAG_PERMISSION_REVIEW_REQUIRED, 0)
+ }
+ }
+
+ private fun launchApp(
+ createChannels: Boolean = true,
+ createChannelsDelayed: Boolean = false,
+ deleteChannels: Boolean = false,
+ requestPermissions: Boolean = false,
+ requestPermissionsDelayed: Boolean = false
+ ) {
+ val intent = Intent(INTENT_ACTION)
+ intent.`package` = APP_PACKAGE_NAME
+ intent.putExtra(EXTRA_CREATE_CHANNELS, createChannels)
+ if (!createChannels) {
+ intent.putExtra(EXTRA_CREATE_CHANNELS_DELAYED, createChannelsDelayed)
+ }
+ intent.putExtra(EXTRA_DELETE_CHANNELS_ON_CLOSE, deleteChannels)
+ intent.putExtra(EXTRA_REQUEST_PERMISSIONS, requestPermissions)
+ if (!requestPermissions) {
+ intent.putExtra(EXTRA_REQUEST_PERMISSIONS_DELAYED, requestPermissionsDelayed)
+ }
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+
+ context.startActivity(intent)
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt
new file mode 100644
index 0000000..e88572d
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts
+
+import android.app.Activity
+import android.app.AppOpsManager
+import android.content.ComponentName
+import android.content.Intent
+import android.location.LocationManager
+import android.os.Build
+import android.provider.DeviceConfig
+import android.support.test.uiautomator.By
+import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.AppOpsUtils.setOpMode
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.modules.utils.build.SdkLevel
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests permission attribution for location providers.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+class PermissionAttributionTest : BasePermissionHubTest() {
+ private val micLabel = packageManager.getPermissionGroupInfo(
+ android.Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
+ val locationManager = context.getSystemService(LocationManager::class.java)!!
+ private var wasEnabled = false
+
+ // Permission history is not available on Auto devices running S or below.
+ @Before
+ fun assumeNotAutoBelowT() {
+ assumeFalse(isAutomotive && !SdkLevel.isAtLeastT())
+ }
+
+ @Before
+ fun installAppLocationProviderAndAllowMockLocation() {
+ installPackage(APP_APK_PATH, grantRuntimePermissions = true)
+ // The package name of a mock location provider is the caller adding it, so we have to let
+ // the test app add itself.
+ setOpMode(APP_PACKAGE_NAME, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpsManager.MODE_ALLOWED)
+ }
+
+ @Before
+ fun setup() {
+ // Allow ourselves to reliably remove the test location provider.
+ setOpMode(
+ context.packageName, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpsManager.MODE_ALLOWED
+ )
+ wasEnabled = setSubattributionEnabledStateIfNeeded(true)
+ }
+
+ @After
+ fun teardown() {
+ locationManager.removeTestProvider(APP_PACKAGE_NAME)
+ if (!wasEnabled) {
+ setSubattributionEnabledStateIfNeeded(false)
+ }
+ }
+
+ @Test
+ fun testLocationProviderAttributionForMicrophone() {
+ enableAppAsLocationProvider()
+ useMicrophone()
+ openMicrophoneTimeline()
+
+ waitFindObject(By.descContains(micLabel))
+ waitFindObject(By.textContains(APP_LABEL))
+ waitFindObject(By.textContains(ATTRIBUTION_LABEL))
+ }
+
+ private fun enableAppAsLocationProvider() {
+ // Add the test app as location provider.
+ val future = startActivityForFuture(
+ Intent().apply {
+ component = ComponentName(
+ APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.AddLocationProviderActivity"
+ )
+ }
+ )
+ val result = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ assertEquals(Activity.RESULT_OK, result.resultCode)
+ assertTrue(
+ callWithShellPermissionIdentity {
+ locationManager.isProviderPackage(APP_PACKAGE_NAME)
+ }
+ )
+ }
+
+ private fun useMicrophone() {
+ val future = startActivityForFuture(
+ Intent().apply {
+ component = ComponentName(
+ APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.UseMicrophoneActivity"
+ )
+ }
+ )
+ val result = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ assertEquals(Activity.RESULT_OK, result.resultCode)
+ }
+
+ private fun setSubattributionEnabledStateIfNeeded(shouldBeEnabled: Boolean): Boolean {
+ var currentlyEnabled = false
+ runWithShellPermissionIdentity {
+ currentlyEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ FLAG_SUBATTRIBUTION, false)
+ if (currentlyEnabled != shouldBeEnabled) {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, FLAG_SUBATTRIBUTION,
+ shouldBeEnabled.toString(), false)
+ }
+ }
+ return currentlyEnabled
+ }
+
+ companion object {
+ const val APP_APK_PATH = "$APK_DIRECTORY/CtsAccessMicrophoneAppLocationProvider.apk"
+ const val APP_PACKAGE_NAME = "android.permission3.cts.accessmicrophoneapplocationprovider"
+ const val APP_LABEL = "LocationProviderWithMicApp"
+ const val ATTRIBUTION_LABEL = "Attribution Label"
+ const val FLAG_SUBATTRIBUTION = "permissions_hub_subattribution_enabled"
+ }
+}
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt
index 794a731..695d802 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt
@@ -18,15 +18,17 @@
import android.Manifest
import android.content.Intent
-import android.content.pm.PackageManager.FEATURE_LEANBACK
-import android.content.pm.PackageManager.FEATURE_AUTOMOTIVE
import android.os.Build
+import android.provider.DeviceConfig
+import android.provider.DeviceConfig.NAMESPACE_PRIVACY
import android.support.test.uiautomator.By
import androidx.test.filters.SdkSuppress
import com.android.compatibility.common.util.SystemUtil
+import com.android.modules.utils.build.SdkLevel
import org.junit.After
import org.junit.Assume.assumeFalse
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
private const val APP_LABEL_1 = "CtsMicAccess"
@@ -37,22 +39,28 @@
private const val HISTORY_PREFERENCE_ICON = "permission_history_icon"
private const val HISTORY_PREFERENCE_TIME = "permission_history_time"
private const val SHOW_SYSTEM = "Show system"
+private const val SHOW_7_DAYS = "Show 7 days"
+private const val SHOW_24_HOURS = "Show 24 hours"
private const val MORE_OPTIONS = "More options"
+private const val TIMELINE_7_DAYS_DESCRIPTION = "in the past 7 days"
+private const val DASHBOARD_7_DAYS_DESCRIPTION = "7 days"
+private const val PRIV_DASH_7_DAY_ENABLED = "privacy_dashboard_7_day_toggle"
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
-class PermissionHistoryTest : BasePermissionTest() {
+class PermissionHistoryTest : BasePermissionHubTest() {
private val micLabel = packageManager.getPermissionGroupInfo(
- Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
+ Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
+ private var was7DayToggleEnabled = false
// Permission history is not available on TV devices.
@Before
- fun assumeNotTv() =
- assumeFalse(packageManager.hasSystemFeature(FEATURE_LEANBACK))
+ fun assumeNotTv() = assumeFalse(isTv)
- // Permission history is not available on Auto devices.
+ // Permission history is not available on Auto devices running S or below.
@Before
- fun assumeNotAuto() =
- assumeFalse(packageManager.hasSystemFeature(FEATURE_AUTOMOTIVE))
+ fun assumeNotAutoBelowT() {
+ assumeFalse(isAutomotive && !SdkLevel.isAtLeastT())
+ }
@Before
fun installApps() {
@@ -68,6 +76,24 @@
uninstallPackage(APP2_PACKAGE_NAME, requireSuccess = false)
}
+ @Before
+ fun setUpTest() {
+ SystemUtil.runWithShellPermissionIdentity {
+ was7DayToggleEnabled = DeviceConfig.getBoolean(NAMESPACE_PRIVACY,
+ PRIV_DASH_7_DAY_ENABLED, false)
+ DeviceConfig.setProperty(NAMESPACE_PRIVACY,
+ PRIV_DASH_7_DAY_ENABLED, true.toString(), false)
+ }
+ }
+
+ @After
+ fun tearDownTest() {
+ SystemUtil.runWithShellPermissionIdentity {
+ DeviceConfig.setProperty(NAMESPACE_PRIVACY,
+ PRIV_DASH_7_DAY_ENABLED, was7DayToggleEnabled.toString(), false)
+ }
+ }
+
@Test
fun testMicrophoneAccessShowsUpOnPrivacyDashboard() {
openMicrophoneApp(INTENT_ACTION_1)
@@ -100,6 +126,56 @@
}
@Test
+ fun testToggleFrom24HoursTo7Days() {
+ openMicrophoneApp(INTENT_ACTION_1)
+ waitFindObject(By.textContains(APP_LABEL_1))
+
+ openPermissionDashboard()
+ waitFindObject(By.descContains(MORE_OPTIONS)).click()
+ try {
+ waitFindObject(By.text(SHOW_7_DAYS)).click()
+ } catch (exception: RuntimeException) {
+ // If privacy dashboard was set to 7d instead of 24h,
+ // it will not be able to find the "Show 7 days" option.
+ // This block is to toggle it back to 24h if that happens.
+ waitFindObject(By.text(SHOW_24_HOURS)).click()
+ waitFindObject(By.descContains(MORE_OPTIONS)).click()
+ waitFindObject(By.text(SHOW_7_DAYS)).click()
+ }
+
+ waitFindObject(By.res("android:id/title").textContains("Microphone"))
+ waitFindObject(By.textContains(DASHBOARD_7_DAYS_DESCRIPTION))
+
+ pressBack()
+ }
+
+ @Test
+ fun testToggleFrom24HoursTo7DaysInTimeline() {
+ openMicrophoneApp(INTENT_ACTION_1)
+ waitFindObject(By.textContains(APP_LABEL_1))
+
+ openMicrophoneTimeline()
+ waitFindObject(By.descContains(MORE_OPTIONS)).click()
+ try {
+ waitFindObject(By.text(SHOW_7_DAYS)).click()
+ } catch (exception: RuntimeException) {
+ // If privacy dashboard was set to 7d instead of 24h,
+ // it will not be able to find the "Show 7 days" option.
+ // This block is to toggle it back to 24h if that happens.
+ waitFindObject(By.text(SHOW_24_HOURS)).click()
+ waitFindObject(By.descContains(MORE_OPTIONS)).click()
+ waitFindObject(By.text(SHOW_7_DAYS)).click()
+ }
+
+ waitFindObject(By.descContains(micLabel))
+ waitFindObject(By.textContains(APP_LABEL_1))
+ waitFindObject(By.textContains(TIMELINE_7_DAYS_DESCRIPTION))
+
+ pressBack()
+ }
+
+ @Test
+ @Ignore
fun testMicrophoneTimelineWithOneApp() {
openMicrophoneApp(INTENT_ACTION_1)
waitFindObject(By.textContains(APP_LABEL_1))
@@ -135,15 +211,6 @@
})
}
- private fun openMicrophoneTimeline() {
- SystemUtil.runWithShellPermissionIdentity {
- context.startActivity(Intent(Intent.ACTION_REVIEW_PERMISSION_HISTORY).apply {
- putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, Manifest.permission_group.MICROPHONE)
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- })
- }
- }
-
private fun openPermissionDashboard() {
SystemUtil.runWithShellPermissionIdentity {
context.startActivity(Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE).apply {
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTapjackingTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTapjackingTest.kt
new file mode 100644
index 0000000..fcd9036
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTapjackingTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts
+
+import android.content.ComponentName
+import android.content.Intent
+import android.support.test.uiautomator.By
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+import java.lang.Exception
+
+/**
+ * Tests permission review screen can't be tapjacked
+ */
+class PermissionReviewTapjackingTest : BaseUsePermissionTest() {
+
+ companion object {
+ const val HELPER_APP_OVERLAY = "$APK_DIRECTORY/CtsHelperAppOverlay.apk"
+ private const val HELPER_PACKAGE_NAME = "android.permission3.cts.helper.overlay"
+ }
+
+ @Before
+ fun installApp22AndApprovePermissionReview() {
+ assumeFalse(packageManager.arePermissionsIndividuallyControlled())
+
+ installPackage(APP_APK_PATH_22)
+ installPackage(HELPER_APP_OVERLAY)
+
+ SystemUtil.runShellCommandOrThrow(
+ "appops set $HELPER_PACKAGE_NAME android:system_alert_window allow")
+ }
+
+ @After
+ fun uninstallPackages() {
+ SystemUtil.runShellCommandOrThrow(
+ "pm uninstall $APP_PACKAGE_NAME")
+ SystemUtil.runShellCommandOrThrow(
+ "pm uninstall $HELPER_PACKAGE_NAME")
+ }
+
+ @Test
+ fun testOverlaysAreHidden() {
+ context.startActivity(Intent()
+ .setComponent(ComponentName(HELPER_PACKAGE_NAME,
+ "$HELPER_PACKAGE_NAME.OverlayActivity"))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
+ findOverlay()
+
+ context.startActivity(Intent()
+ .setComponent(ComponentName(APP_PACKAGE_NAME,
+ "$APP_PACKAGE_NAME.FinishOnCreateActivity"))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ )
+
+ waitFindObject(By.res("com.android.permissioncontroller:id/permissions_message"))
+
+ try {
+ findOverlay()
+ Assert.fail("Overlay was displayed")
+ } catch (e: Exception) {
+ // expected
+ }
+
+ pressHome()
+ findOverlay()
+ }
+
+ private fun findOverlay() = waitFindObject(By.text("Find me!"))
+}
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt
index 6b8678d..6e0f72c 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt
@@ -20,11 +20,13 @@
import android.content.ComponentName
import android.content.Intent
import android.content.pm.PackageManager
+import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.ResultReceiver
import android.support.test.uiautomator.By
+import androidx.test.filters.SdkSuppress
import androidx.test.runner.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
@@ -114,6 +116,15 @@
}
@Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "TIRAMISU")
+ fun testNotificationPermissionAddedToReview() {
+ startAppActivityAndAssertResultCode(Activity.RESULT_CANCELED) {
+ waitFindObject(By.text("Notifications"), 5000L)
+ clickPermissionReviewCancel()
+ }
+ }
+
+ @Test
fun testReviewPermissionWhenServiceIsBound() {
val results = LinkedBlockingQueue<Int>()
// We are starting a activity instead of the service directly, because
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionSplitTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionSplitTest.kt
index 22074f7..9550934 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionSplitTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionSplitTest.kt
@@ -16,6 +16,8 @@
package android.permission3.cts
+import android.os.Build
+import androidx.test.filters.SdkSuppress
import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Test
@@ -53,6 +55,27 @@
testLocationPermissionSplit(false)
}
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ @Test
+ fun testBodySensorSplit() {
+ installPackage(APP_APK_PATH_31)
+ testBodySensorPermissionSplit(true)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ @Test
+ fun testBodySensorSplit32() {
+ installPackage(APP_APK_PATH_32)
+ testBodySensorPermissionSplit(true)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ @Test
+ fun testBodySensorNonSplit() {
+ installPackage(APP_APK_PATH_LATEST)
+ testBodySensorPermissionSplit(false)
+ }
+
private fun testLocationPermissionSplit(expectSplit: Boolean) {
assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, false)
assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, false)
@@ -69,4 +92,21 @@
assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, expectSplit)
}
+
+ private fun testBodySensorPermissionSplit(expectSplit: Boolean) {
+ assertAppHasPermission(android.Manifest.permission.BODY_SENSORS, false)
+ assertAppHasPermission(android.Manifest.permission.BODY_SENSORS_BACKGROUND, false)
+
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.BODY_SENSORS to true
+ ) {
+ if (expectSplit) {
+ clickPermissionRequestSettingsLinkAndAllowAlways()
+ } else {
+ clickPermissionRequestAllowForegroundButton()
+ }
+ }
+
+ assertAppHasPermission(android.Manifest.permission.BODY_SENSORS_BACKGROUND, expectSplit)
+ }
}
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt
index 905d46e..b5b22cf 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt
@@ -20,7 +20,9 @@
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Point
+import android.os.Build
import android.support.test.uiautomator.By
+import androidx.test.filters.SdkSuppress
import com.android.compatibility.common.util.SystemUtil
import org.junit.Assume.assumeFalse
import org.junit.Before
@@ -55,6 +57,7 @@
tryClicking(buttonCenter)
}
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
@Test
fun testTapjackGrantDialog_partialOverlay() {
// PermissionController for television uses a floating window.
@@ -75,10 +78,10 @@
// Wait for overlay to hide the dialog
context.sendBroadcast(Intent(ACTION_SHOW_OVERLAY)
.putExtra(EXTRA_FULL_OVERLAY, false)
- .putExtra(DIALOG_LEFT, overlayButtonBounds.left)
- .putExtra(DIALOG_TOP, overlayButtonBounds.top)
- .putExtra(DIALOG_RIGHT, overlayButtonBounds.right)
- .putExtra(MESSAGE_BOTTOM, overlayButtonBounds.bottom))
+ .putExtra(OVERLAY_LEFT, overlayButtonBounds.left)
+ .putExtra(OVERLAY_TOP, overlayButtonBounds.top)
+ .putExtra(OVERLAY_RIGHT, overlayButtonBounds.right)
+ .putExtra(OVERLAY_BOTTOM, overlayButtonBounds.bottom))
waitFindObject(By.res("android.permission3.cts.usepermission:id/overlay"))
tryClicking(foregroundButtonCenter)
@@ -101,19 +104,16 @@
// Permission should not be granted
assertAppHasPermission(ACCESS_FINE_LOCATION, false)
- // On Automotive the dialog gets closed by the tapjacking activity popping up
- if (!isAutomotive) {
- // Verify that clicking the dialog without the overlay still works
- context.sendBroadcast(Intent(ACTION_HIDE_OVERLAY))
- SystemUtil.eventually({
- if (packageManager.checkPermission(ACCESS_FINE_LOCATION, APP_PACKAGE_NAME) ==
- PackageManager.PERMISSION_DENIED) {
- uiDevice.click(buttonCenter.x, buttonCenter.y)
- Thread.sleep(100)
- }
- assertAppHasPermission(ACCESS_FINE_LOCATION, true)
- }, 10000)
- }
+ // Verify that clicking the dialog without the overlay still works
+ context.sendBroadcast(Intent(ACTION_HIDE_OVERLAY))
+ SystemUtil.eventually({
+ if (packageManager.checkPermission(ACCESS_FINE_LOCATION, APP_PACKAGE_NAME) ==
+ PackageManager.PERMISSION_DENIED) {
+ uiDevice.click(buttonCenter.x, buttonCenter.y)
+ Thread.sleep(100)
+ }
+ assertAppHasPermission(ACCESS_FINE_LOCATION, true)
+ }, 10000)
}
companion object {
@@ -122,9 +122,9 @@
const val EXTRA_FULL_OVERLAY = "android.permission3.cts.usepermission.extra.FULL_OVERLAY"
- const val DIALOG_LEFT = "android.permission3.cts.usepermission.extra.DIALOG_LEFT"
- const val DIALOG_TOP = "android.permission3.cts.usepermission.extra.DIALOG_TOP"
- const val DIALOG_RIGHT = "android.permission3.cts.usepermission.extra.DIALOG_RIGHT"
- const val MESSAGE_BOTTOM = "android.permission3.cts.usepermission.extra.MESSAGE_BOTTOM"
+ const val OVERLAY_LEFT = "android.permission3.cts.usepermission.extra.OVERLAY_LEFT"
+ const val OVERLAY_TOP = "android.permission3.cts.usepermission.extra.OVERLAY_TOP"
+ const val OVERLAY_RIGHT = "android.permission3.cts.usepermission.extra.OVERLAY_RIGHT"
+ const val OVERLAY_BOTTOM = "android.permission3.cts.usepermission.extra.OVERLAY_BOTTOM"
}
}
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
index 26fe615..f9c4496 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
@@ -16,8 +16,10 @@
package android.permission3.cts
+import android.content.pm.PackageManager
import androidx.test.filters.FlakyTest
-import com.android.modules.utils.build.SdkLevel
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.Assert
import org.junit.Assume
import org.junit.Before
import org.junit.Test
@@ -182,11 +184,7 @@
// Request the permission and allow it
// Make sure the permission is granted
requestAppPermissionsAndAssertResult(android.Manifest.permission.CAMERA to true) {
- if (SdkLevel.isAtLeastS()) {
- clickPermissionRequestAllowForegroundButton()
- } else {
- clickPermissionRequestAllowButton()
- }
+ clickPermissionRequestAllowForegroundButton()
}
}
@@ -248,11 +246,10 @@
android.Manifest.permission.READ_SMS,
android.Manifest.permission.CALL_PHONE,
android.Manifest.permission.RECORD_AUDIO,
- android.Manifest.permission.BODY_SENSORS,
android.Manifest.permission.CAMERA,
android.Manifest.permission.READ_EXTERNAL_STORAGE, targetSdk = 23
)
- // Don't use UI for granting location permission as this shows another dialog
+ // Don't use UI for granting location and sensor permissions as they show another dialog
uiAutomation.grantRuntimePermission(
APP_PACKAGE_NAME, android.Manifest.permission.ACCESS_FINE_LOCATION
)
@@ -262,6 +259,9 @@
uiAutomation.grantRuntimePermission(
APP_PACKAGE_NAME, android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
)
+ uiAutomation.grantRuntimePermission(
+ APP_PACKAGE_NAME, android.Manifest.permission.BODY_SENSORS
+ )
uninstallPackage(APP_PACKAGE_NAME)
installPackage(APP_APK_PATH_23)
@@ -290,11 +290,7 @@
null to false,
android.Manifest.permission.RECORD_AUDIO to true
) {
- if (SdkLevel.isAtLeastS()) {
- clickPermissionRequestAllowForegroundButton()
- } else {
- clickPermissionRequestAllowButton()
- }
+ clickPermissionRequestAllowForegroundButton()
clickPermissionRequestAllowButton()
}
}
@@ -306,6 +302,34 @@
requestAppPermissionsAndAssertResult(INVALID_PERMISSION to false) {}
}
+ @Test
+ fun testAskButtonSetsFlags() {
+ Assume.assumeFalse("other form factors might not support the ask button",
+ isTv || isAutomotive || isWatch)
+
+ grantAppPermissions(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, targetSdk = 23)
+ assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, true)
+ assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, true)
+ assertAppHasPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION, true)
+
+ revokeAppPermissions(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, targetSdk = 23)
+ SystemUtil.runWithShellPermissionIdentity {
+ val perms = listOf(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+ android.Manifest.permission.ACCESS_FINE_LOCATION,
+ android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ for (perm in perms) {
+ var flags = packageManager.getPermissionFlags(perm, APP_PACKAGE_NAME, context.user)
+ Assert.assertEquals("USER_SET should not be set for $perm", 0,
+ flags and PackageManager.FLAG_PERMISSION_USER_SET)
+ Assert.assertEquals("USER_FIXED should not be set for $perm", 0,
+ flags and PackageManager.FLAG_PERMISSION_USER_FIXED)
+ Assert.assertEquals("ONE_TIME should be set for $perm",
+ PackageManager.FLAG_PERMISSION_ONE_TIME,
+ flags and PackageManager.FLAG_PERMISSION_ONE_TIME)
+ }
+ }
+ }
+
private fun denyPermissionRequestWithPrejudice() {
if (isTv || isWatch) {
clickPermissionRequestDontAskAgainButton()
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTest30WithBluetooth.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTest30WithBluetooth.kt
index 3cef547..c3b2bb5 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTest30WithBluetooth.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTest30WithBluetooth.kt
@@ -26,8 +26,10 @@
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
+import android.location.LocationManager
import android.os.Build
import android.os.Process
+import android.os.UserHandle
import androidx.test.InstrumentationRegistry
import androidx.test.filters.SdkSuppress
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
@@ -53,6 +55,8 @@
"android.permission3.cts.usepermission"
private lateinit var bluetoothAdapter: BluetoothAdapter
private var bluetoothAdapterWasEnabled: Boolean = false
+ private val locationManager = context.getSystemService(LocationManager::class.java)!!
+ private var locationWasEnabled: Boolean = false
private enum class BluetoothScanResult {
UNKNOWN, ERROR, EXCEPTION, EMPTY, FILTERED, FULL
@@ -78,6 +82,28 @@
enableTestMode()
}
+ @Before
+ fun enableLocation() {
+ val userHandle: UserHandle = Process.myUserHandle()
+ locationWasEnabled = locationManager.isLocationEnabledForUser(userHandle)
+ if (!locationWasEnabled) {
+ runWithShellPermissionIdentity {
+ locationManager.setLocationEnabledForUser(true, userHandle)
+ }
+ }
+ }
+
+ @After
+ fun disableLocation() {
+ val userHandle: UserHandle = Process.myUserHandle()
+
+ if (!locationWasEnabled) {
+ runWithShellPermissionIdentity {
+ locationManager.setLocationEnabledForUser(false, userHandle)
+ }
+ }
+ }
+
@After
fun disableBluetooth() {
assumeTrue(supportsBluetooth())
@@ -91,6 +117,9 @@
@Test
fun testGivenBluetoothIsDeniedWhenScanIsAttemptedThenThenGetEmptyScanResult() {
+ assertTrue("Please enable location to run this test. Bluetooth scanning " +
+ "requires location to be enabled.", locationManager.isLocationEnabled())
+
assertBluetoothRevokedCompatState(revoked = false)
// Should return empty while the app does not have location
assertEquals(BluetoothScanResult.EMPTY, scanForBluetoothDevices())
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTestLatest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTestLatest.kt
new file mode 100644
index 0000000..d6e7a69
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTestLatest.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts
+
+import android.os.Build
+import androidx.test.filters.SdkSuppress
+import org.junit.Assert
+import org.junit.Test
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+class PermissionTestLatest : BaseUsePermissionTest() {
+ /**
+ * Not exactly a cts type test but it needs to be run continuously. This test is supposed to
+ * start failing once the sdk integer is decided for T. This is important to have because it was
+ * assumed that the sdk int will be 33 in frameworks/base/data/etc/platform.xml. This test
+ * should be removed once the sdk is finalized.
+ */
+ @Test
+ fun testTApiVersionCodeIsNotSet() {
+ Assert.assertEquals(Build.VERSION_CODES.S_V2, Build.VERSION.SDK_INT)
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt b/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt
new file mode 100644
index 0000000..9dc0b27
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts
+
+import android.content.Intent
+import android.hardware.SensorPrivacyManager
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
+import android.location.LocationManager
+import android.os.Build
+import android.provider.DeviceConfig
+import android.provider.Settings
+import android.support.test.uiautomator.By
+import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Banner card display tests on sensors being blocked
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+class SensorBlockedBannerTest : BaseUsePermissionTest() {
+ companion object {
+ const val LOCATION = -1
+ const val WARNING_BANNER_ENABLED = "warning_banner_enabled"
+ }
+
+ val sensorPrivacyManager = context.getSystemService(SensorPrivacyManager::class.java)!!
+ val locationManager = context.getSystemService(LocationManager::class.java)!!
+ lateinit var originalEnabledValue: String
+
+ private val permToLabel = mapOf(CAMERA to "privdash_label_camera",
+ MICROPHONE to "privdash_label_microphone",
+ LOCATION to "privdash_label_location")
+
+ private val permToTitle = mapOf(CAMERA to "blocked_camera_title",
+ MICROPHONE to "blocked_microphone_title",
+ LOCATION to "blocked_location_title")
+
+ @Before
+ fun setup() {
+ Assume.assumeFalse(isTv)
+ installPackage(APP_APK_PATH_31)
+ runWithShellPermissionIdentity {
+ originalEnabledValue = DeviceConfig.getString(DeviceConfig.NAMESPACE_PRIVACY,
+ WARNING_BANNER_ENABLED, false.toString())
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+ WARNING_BANNER_ENABLED, true.toString(), false)
+ }
+ }
+
+ @After
+ fun restoreWarningBannerState() {
+ runWithShellPermissionIdentity {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+ WARNING_BANNER_ENABLED, originalEnabledValue, false)
+ }
+ }
+
+ private fun navigateAndTest(sensor: Int) {
+ val permLabel = permToLabel.getOrDefault(sensor, "Break")
+ val intent = Intent(Settings.ACTION_PRIVACY_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(intent)
+ click(By.text(getPermissionControllerString("app_permission_manager")))
+ click(By.text(getPermissionControllerString(permLabel)))
+ val bannerTitle = permToTitle.getOrDefault(sensor, "Break")
+ waitFindObject(By.text(getPermissionControllerString(bannerTitle)))
+ pressBack()
+ pressBack()
+ pressBack()
+ }
+
+ private fun runSensorTest(sensor: Int) {
+ val blocked = isSensorPrivacyEnabled(sensor)
+ if (!blocked) {
+ setSensor(sensor, true)
+ }
+ navigateAndTest(sensor)
+ if (!blocked) {
+ setSensor(sensor, false)
+ }
+ }
+
+ @Test
+ fun testCameraCardDisplayed() {
+ Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(CAMERA))
+ runSensorTest(CAMERA)
+ }
+
+ @Test
+ fun testMicCardDisplayed() {
+ Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(MICROPHONE))
+ runSensorTest(MICROPHONE)
+ }
+
+ @Test
+ fun testLocationCardDisplayed() {
+ runSensorTest(LOCATION)
+ }
+
+ private fun setSensor(sensor: Int, enable: Boolean) {
+ if (sensor == LOCATION) {
+ runWithShellPermissionIdentity {
+ locationManager.setLocationEnabledForUser(!enable,
+ android.os.Process.myUserHandle())
+ if (enable) {
+ try {
+ waitFindObjectOrNull(By.text("CLOSE"))?.click()
+ } catch (e: Exception) {
+ // Do nothing, warning didn't show up so test can proceed
+ }
+ }
+ }
+ } else {
+ runWithShellPermissionIdentity {
+ sensorPrivacyManager.setSensorPrivacy(SensorPrivacyManager.Sources.OTHER,
+ sensor, enable)
+ }
+ }
+ }
+
+ private fun isSensorPrivacyEnabled(sensor: Int): Boolean {
+ return if (sensor == LOCATION) {
+ callWithShellPermissionIdentity {
+ !locationManager.isLocationEnabled()
+ }
+ } else {
+ callWithShellPermissionIdentity {
+ sensorPrivacyManager.isSensorPrivacyEnabled(sensor)
+ }
+ }
+ }
+}
diff --git a/tests/tests/permission4/AndroidTest.xml b/tests/tests/permission4/AndroidTest.xml
index 71353aa..78fe62f 100644
--- a/tests/tests/permission4/AndroidTest.xml
+++ b/tests/tests/permission4/AndroidTest.xml
@@ -21,6 +21,7 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
diff --git a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
index e8f503e..4128de9 100644
--- a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
+++ b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
@@ -176,8 +176,10 @@
} else if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
assertCarIndicatorsShown(useMic, useCamera, useHotword)
} else {
+ // Hotword gets remapped to RECORD_AUDIO on handheld, so handheld should show a mic
+ // indicator
uiDevice.openQuickSettings()
- assertPrivacyChipAndIndicatorsPresent(useMic, useCamera)
+ assertPrivacyChipAndIndicatorsPresent(useMic || useHotword, useCamera)
}
}
@@ -201,7 +203,18 @@
private fun assertCarIndicatorsShown(useMic: Boolean, useCamera: Boolean, useHotword: Boolean) {
// Ensure the privacy chip is present (or not)
- val chipFound = isChipPresent()
+ var chipFound = false
+ try {
+ eventually {
+ val privacyChip = uiDevice.findObject(By.res(PRIVACY_CHIP_ID))
+ assertNotNull("view with id $PRIVACY_CHIP_ID not found", privacyChip)
+ privacyChip.click()
+ chipFound = true
+ }
+ } catch (e: Exception) {
+ // Handle more gracefully below
+ }
+
if (useMic) {
assertTrue("Did not find chip", chipFound)
} else if (useHotword || useCamera) {
@@ -227,13 +240,11 @@
}
private fun assertPrivacyChipAndIndicatorsPresent(useMic: Boolean, useCamera: Boolean) {
- // Ensure the privacy chip is present (or not)
- val chipFound = isChipPresent()
- if (useMic || useCamera) {
- assertTrue("Did not find chip", chipFound)
- } else { // hotword
- assertFalse("Found chip, but did not expect to", chipFound)
- return
+ // Ensure the privacy chip is present
+ eventually {
+ val privacyChip = uiDevice.findObject(UiSelector().resourceId(PRIVACY_CHIP_ID))
+ assertTrue("view with id $PRIVACY_CHIP_ID not found", privacyChip.exists())
+ privacyChip.click()
}
eventually {
@@ -250,21 +261,6 @@
}
}
- private fun isChipPresent(): Boolean {
- var chipFound = false
- try {
- eventually {
- val privacyChip = uiDevice.findObject(By.res(PRIVACY_CHIP_ID))
- assertNotNull("view with id $PRIVACY_CHIP_ID not found", privacyChip)
- privacyChip.click()
- chipFound = true
- }
- } catch (e: Exception) {
- // Handle more gracefully after
- }
- return chipFound
- }
-
private fun pressBack() {
uiDevice.pressBack()
waitForIdle()
diff --git a/tests/tests/permission5/AndroidTest.xml b/tests/tests/permission5/AndroidTest.xml
index d449d9f..953df0d 100644
--- a/tests/tests/permission5/AndroidTest.xml
+++ b/tests/tests/permission5/AndroidTest.xml
@@ -21,6 +21,7 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
diff --git a/tests/tests/permission5/src/android/permission5/cts/PermissionCheckerTest.kt b/tests/tests/permission5/src/android/permission5/cts/PermissionCheckerTest.kt
new file mode 100644
index 0000000..b7762c6
--- /dev/null
+++ b/tests/tests/permission5/src/android/permission5/cts/PermissionCheckerTest.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission5.cts
+
+import android.app.AppOpsManager
+import android.content.AttributionSource
+import android.os.Process
+import android.permission.PermissionManager
+import android.platform.test.annotations.AppModeFull
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+@AppModeFull(reason = "Instant apps cannot hold READ_CALENDAR")
+class PermissionCheckerTest {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context = instrumentation.getContext()
+ private val packageManager = context.packageManager
+ private val appOpsManager = context.getSystemService(AppOpsManager::class.java)
+ private val permissionManager = context.getSystemService(PermissionManager::class.java)
+ private val currentUser = Process.myUserHandle()
+
+ private val helperUid = packageManager.getPackageUid(HELPER_PACKAGE_NAME, 0)
+ private val helperAttributionSource = AttributionSource.Builder(helperUid)
+ .setPackageName(HELPER_PACKAGE_NAME)
+ .build()
+
+ @Before
+ @After
+ fun resetHelperPermissionState() {
+ runWithShellPermissionIdentity {
+ Thread.sleep(1000)
+ packageManager.grantRuntimePermission(
+ HELPER_PACKAGE_NAME, HELPER_PERMISSION_NAME, currentUser
+ )
+ Thread.sleep(1000)
+ appOpsManager.setUidMode(HELPER_APP_OP_NAME, helperUid, AppOpsManager.MODE_ALLOWED)
+ Thread.sleep(1000)
+ }
+ }
+
+ @Test
+ fun testCheckPermissionForPreflight() {
+ assertThat(
+ permissionManager.checkPermissionForPreflight(
+ HELPER_PERMISSION_NAME, helperAttributionSource
+ )
+ ).isEqualTo(PermissionManager.PERMISSION_GRANTED)
+
+ runWithShellPermissionIdentity {
+ appOpsManager.setUidMode(HELPER_APP_OP_NAME, helperUid, AppOpsManager.MODE_IGNORED)
+ }
+ assertThat(
+ permissionManager.checkPermissionForPreflight(
+ HELPER_PERMISSION_NAME, helperAttributionSource
+ )
+ ).isEqualTo(PermissionManager.PERMISSION_SOFT_DENIED)
+
+ runWithShellPermissionIdentity {
+ packageManager.revokeRuntimePermission(
+ HELPER_PACKAGE_NAME, HELPER_PERMISSION_NAME, currentUser
+ )
+ }
+ assertThat(
+ permissionManager.checkPermissionForPreflight(
+ HELPER_PERMISSION_NAME, helperAttributionSource
+ )
+ ).isEqualTo(PermissionManager.PERMISSION_HARD_DENIED)
+ }
+
+ @Test
+ fun testCheckPermissionForDataDelivery() {
+ // checkPermissionForDataDelivery() requires UPDATE_APP_OPS_STATS.
+ runWithShellPermissionIdentity {
+ assertThat(
+ permissionManager.checkPermissionForDataDelivery(
+ HELPER_PERMISSION_NAME, helperAttributionSource, null
+ )
+ ).isEqualTo(PermissionManager.PERMISSION_GRANTED)
+
+ appOpsManager.setUidMode(HELPER_APP_OP_NAME, helperUid, AppOpsManager.MODE_IGNORED)
+ assertThat(
+ permissionManager.checkPermissionForDataDelivery(
+ HELPER_PERMISSION_NAME, helperAttributionSource, null
+ )
+ ).isEqualTo(PermissionManager.PERMISSION_SOFT_DENIED)
+
+ packageManager.revokeRuntimePermission(
+ HELPER_PACKAGE_NAME, HELPER_PERMISSION_NAME, currentUser
+ )
+ assertThat(
+ permissionManager.checkPermissionForDataDelivery(
+ HELPER_PERMISSION_NAME, helperAttributionSource, null
+ )
+ ).isEqualTo(PermissionManager.PERMISSION_HARD_DENIED)
+ }
+ }
+
+ @Test
+ fun testCheckPermissionForDataDeliveryFromDataSource() {
+ runWithShellPermissionIdentity({
+ assertThat(
+ permissionManager.checkPermissionForDataDeliveryFromDataSource(
+ HELPER_PERMISSION_NAME, helperAttributionSource, null
+ )
+ ).isEqualTo(PermissionManager.PERMISSION_GRANTED)
+ }, android.Manifest.permission.UPDATE_APP_OPS_STATS)
+
+ runWithShellPermissionIdentity {
+ appOpsManager.setUidMode(HELPER_APP_OP_NAME, helperUid, AppOpsManager.MODE_IGNORED)
+ }
+
+ runWithShellPermissionIdentity({
+ assertThat(
+ permissionManager.checkPermissionForDataDeliveryFromDataSource(
+ HELPER_PERMISSION_NAME, helperAttributionSource, null
+ )
+ ).isEqualTo(PermissionManager.PERMISSION_SOFT_DENIED)
+ }, android.Manifest.permission.UPDATE_APP_OPS_STATS)
+
+ runWithShellPermissionIdentity {
+ packageManager.revokeRuntimePermission(
+ HELPER_PACKAGE_NAME, HELPER_PERMISSION_NAME, currentUser
+ )
+ }
+
+ runWithShellPermissionIdentity({
+ assertThat(
+ permissionManager.checkPermissionForDataDeliveryFromDataSource(
+ HELPER_PERMISSION_NAME, helperAttributionSource, null
+ )
+ ).isEqualTo(PermissionManager.PERMISSION_SOFT_DENIED)
+ }, android.Manifest.permission.UPDATE_APP_OPS_STATS)
+ }
+
+ companion object {
+ private const val HELPER_PACKAGE_NAME = "android.permission5.cts.blamed"
+ private const val HELPER_PERMISSION_NAME = android.Manifest.permission.READ_CALENDAR
+ private const val HELPER_APP_OP_NAME = AppOpsManager.OPSTR_READ_CALENDAR
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/SettingsPanelTest.java b/tests/tests/provider/src/android/provider/cts/SettingsPanelTest.java
index 97dfd37..71ffe0d 100644
--- a/tests/tests/provider/src/android/provider/cts/SettingsPanelTest.java
+++ b/tests/tests/provider/src/android/provider/cts/SettingsPanelTest.java
@@ -99,6 +99,7 @@
@Test
public void internetDialog_correctPackage() {
+ assumeTrue(mHasTouchScreen);
launchInternetDialog();
String currentPackage = mDevice.getCurrentPackageName();
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
index ff5a1c8..c0e100c 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
@@ -24,6 +24,7 @@
import android.provider.MediaStore.Audio.Media;
import android.provider.cts.ProviderTestUtils;
+import androidx.annotation.RequiresApi;
import androidx.test.filters.SdkSuppress;
import androidx.test.runner.AndroidJUnit4;
@@ -91,7 +92,6 @@
public static final int IS_RINGTONE = 0;
public static final int IS_NOTIFICATION = 0;
public static final int IS_ALARM = 0;
- public static final int IS_RECORDING = 0;
public static final int IS_MUSIC = 1;
public static final int YEAR = 1992;
public static final int TRACK = 1;
@@ -135,7 +135,6 @@
values.put(Media.IS_MUSIC, IS_MUSIC);
values.put(Media.IS_ALARM, IS_ALARM);
values.put(Media.IS_NOTIFICATION, IS_NOTIFICATION);
- values.put(Media.IS_RECORDING, IS_RECORDING);
values.put(Media.IS_RINGTONE, IS_RINGTONE);
return values;
}
@@ -278,6 +277,28 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.S)
+ public static class Audio7 extends Audio1 {
+ public static final int IS_RECORDING = 0;
+
+ private Audio7() {
+ }
+
+ private static Audio7 sInstance = new Audio7();
+
+ public static Audio7 getInstance() {
+ return sInstance;
+ }
+
+ @Override
+ public ContentValues getContentValues(String volumeName) {
+ ContentValues values = super.getContentValues(volumeName);
+ values.put(Media.DATA, values.getAsString(Media.DATA) + ".recording.mp3");
+ values.put(Media.IS_RECORDING, IS_RECORDING);
+ return values;
+ }
+ }
+
@Test
public void testStub() {
// No-op test here to keep atest happy
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
index d5a4e4a..c6768ab 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
@@ -221,12 +221,9 @@
Optional.of("Android/media/android.provider.cts/foo"), null));
assertFalse(updatePlacement(uri,
Optional.of("Android/media/com.example/foo"), null));
- try {
- assertTrue(updatePlacementOrThrow(uri, Optional.of("DCIM"), null));
- } catch (IllegalArgumentException tolerated) {
- // update() above can either succeed or throw IllegalArgumentExxception based on the
- // MediaProvider version.
- }
+ assertTrue(updatePlacement(uri,
+ Optional.of("DCIM"), null));
+ assertFalse(updatePlacement(uri, Optional.of("Android/media"), null));
}
@Test
@@ -236,17 +233,13 @@
final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
mExternalImages, "image/jpeg");
+ assertTrue(updatePlacement(uri,
+ Optional.of("Android/media/android.provider.cts/foo"), null));
assertFalse(updatePlacement(uri,
Optional.of("Android/media/com.example/foo"), null));
assertTrue(updatePlacement(uri,
Optional.of("DCIM"), null));
- try {
- assertTrue(updatePlacementOrThrow(uri,
- Optional.of("Android/media/android.provider.cts/foo"), null));
- } catch (IllegalArgumentException tolerated) {
- // update() above can either succeed or throw IllegalArgumentExxception based on the
- // MediaProvider version.
- }
+ assertFalse(updatePlacement(uri, Optional.of("Android/media"), null));
}
@Test
@@ -327,8 +320,8 @@
ProviderTestUtils.stageFile(R.raw.scenery, file));
}
- private boolean updatePlacementOrThrow(Uri uri, Optional<String> path,
- Optional<String> displayName) throws Exception {
+ private boolean updatePlacement(Uri uri, Optional<String> path, Optional<String> displayName)
+ throws Exception {
final ContentValues values = new ContentValues();
if (path != null) {
values.put(MediaColumns.RELATIVE_PATH, path.orElse(null));
@@ -336,13 +329,8 @@
if (displayName != null) {
values.put(MediaColumns.DISPLAY_NAME, displayName.orElse(null));
}
- return (mContentResolver.update(uri, values, null, null) == 1);
- }
-
- private boolean updatePlacement(Uri uri, Optional<String> path,
- Optional<String> displayName) {
try {
- return updatePlacementOrThrow(uri, path, displayName);
+ return (mContentResolver.update(uri, values, null, null) == 1);
} catch (Exception tolerated) {
return false;
}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
index a196c5a..6a4c199 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
@@ -36,10 +36,10 @@
import android.provider.MediaStore.Audio;
import android.provider.MediaStore.Audio.Media;
import android.provider.MediaStore.Files.FileColumns;
-import android.provider.MediaStore.MediaColumns;
import android.provider.cts.ProviderTestUtils;
import android.provider.cts.R;
import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio7;
import android.provider.cts.media.MediaStoreUtils.PendingParams;
import android.provider.cts.media.MediaStoreUtils.PendingSession;
import android.util.Log;
@@ -157,7 +157,6 @@
assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Media.IS_MUSIC)));
assertEquals(Audio1.IS_NOTIFICATION, c.getInt(c.getColumnIndex(Media.IS_NOTIFICATION)));
assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Media.IS_RINGTONE)));
- assertEquals(Audio1.IS_RECORDING, c.getInt(c.getColumnIndex(Media.IS_RECORDING)));
assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Media.TRACK)));
assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Media.YEAR)));
String titleKey = c.getString(c.getColumnIndex(Media.TITLE_KEY));
@@ -186,6 +185,41 @@
}
}
+ /**
+ * The test case checks below behaviors:
+ * 1. The IS_RECORDING column is in Audio table in MediaStore's database since S OS. Insert with
+ * IS_RECORDING column doesn't fail and we can query the result with IS_RECORDING column.
+ * 2. Validate IS_RECORDING is correctly set for database row.
+ */
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+ public void testStoreNotAudioRecordingMedia() {
+ Audio7 audio7 = Audio7.getInstance();
+ ContentValues values = audio7.getContentValues(mVolumeName);
+ //insert
+ Uri mediaUri = Media.getContentUri(mVolumeName);
+ Uri uri = mContentResolver.insert(mediaUri, values);
+ assertNotNull(uri);
+
+ try (Cursor c = mContentResolver.query(uri, null, null, null, null)) {
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ long id = c.getLong(c.getColumnIndex(Media._ID));
+ assertTrue(id > 0);
+ String expected = audio7.getContentValues(mVolumeName).getAsString(Media.DATA);
+ assertEquals(expected, c.getString(c.getColumnIndex(Media.DATA)));
+ assertEquals(Audio7.MIME_TYPE, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+ assertEquals(Audio7.IS_ALARM, c.getInt(c.getColumnIndex(Media.IS_ALARM)));
+ assertEquals(Audio7.IS_MUSIC, c.getInt(c.getColumnIndex(Media.IS_MUSIC)));
+ assertEquals(Audio7.IS_NOTIFICATION, c.getInt(c.getColumnIndex(Media.IS_NOTIFICATION)));
+ assertEquals(Audio7.IS_RINGTONE, c.getInt(c.getColumnIndex(Media.IS_RINGTONE)));
+ assertEquals(Audio7.IS_RECORDING, c.getInt(c.getColumnIndex(Media.IS_RECORDING)));
+ } finally {
+ // delete
+ mContentResolver.delete(uri, null, null);
+ }
+ }
+
@Test(timeout = 60000)
public void testCanonicalize() throws Exception {
// Remove all audio left over from other tests
diff --git a/tests/tests/provider/src/android/provider/cts/settings/Settings_SecureTest.java b/tests/tests/provider/src/android/provider/cts/settings/Settings_SecureTest.java
index fd84677..693284f 100644
--- a/tests/tests/provider/src/android/provider/cts/settings/Settings_SecureTest.java
+++ b/tests/tests/provider/src/android/provider/cts/settings/Settings_SecureTest.java
@@ -114,10 +114,21 @@
} catch (SettingNotFoundException expected) {
}
+ if (!isFloat(Secure.getString(cr, STRING_VALUE_SETTING))) {
+ try {
+ Secure.getFloat(cr, STRING_VALUE_SETTING);
+ fail("SettingNotFoundException should have been thrown!");
+ } catch (SettingNotFoundException expected) {
+ }
+ }
+ }
+
+ private boolean isFloat(String str) {
try {
- Secure.getFloat(cr, STRING_VALUE_SETTING);
- fail("SettingNotFoundException should have been thrown!");
- } catch (SettingNotFoundException expected) {
+ Float.parseFloat(str);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
}
}
@@ -137,10 +148,21 @@
} catch (SettingNotFoundException expected) {
}
+ if (!isLong(Secure.getString(cr, STRING_VALUE_SETTING))) {
+ try {
+ Secure.getLong(cr, STRING_VALUE_SETTING);
+ fail("SettingNotFoundException should have been thrown!");
+ } catch (SettingNotFoundException expected) {
+ }
+ }
+ }
+
+ private boolean isLong(String str) {
try {
- Secure.getLong(cr, STRING_VALUE_SETTING);
- fail("SettingNotFoundException should have been thrown!");
- } catch (SettingNotFoundException expected) {
+ Long.parseLong(str);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
}
}
diff --git a/tests/tests/provider/src/android/provider/cts/settings/Settings_SystemTest.java b/tests/tests/provider/src/android/provider/cts/settings/Settings_SystemTest.java
index 2fc25b8..2f5155a 100644
--- a/tests/tests/provider/src/android/provider/cts/settings/Settings_SystemTest.java
+++ b/tests/tests/provider/src/android/provider/cts/settings/Settings_SystemTest.java
@@ -147,29 +147,35 @@
@AsbSecurityTest(cveBugId = 156260178)
public void testSystemSettingsRejectInvalidFontSizeScale() throws SettingNotFoundException {
final ContentResolver cr = InstrumentationRegistry.getTargetContext().getContentResolver();
- // First put in a valid value
- assertTrue(System.putFloat(cr, FLOAT_FIELD, 1.15f));
+ final String originalFloatValue = System.getString(cr, FLOAT_FIELD);
try {
- assertFalse(System.putFloat(cr, FLOAT_FIELD, Float.MAX_VALUE));
- fail("Should throw");
- } catch (IllegalArgumentException e) {
+ // First put in a valid value
+ assertTrue(System.putFloat(cr, FLOAT_FIELD, 1.15f));
+ assertEquals(1.15f, System.getFloat(cr, FLOAT_FIELD), 0.001);
+ try {
+ assertFalse(System.putFloat(cr, FLOAT_FIELD, Float.MAX_VALUE));
+ fail("Should throw");
+ } catch (IllegalArgumentException e) {
+ }
+ try {
+ assertFalse(System.putFloat(cr, FLOAT_FIELD, -1f));
+ fail("Should throw");
+ } catch (IllegalArgumentException e) {
+ }
+ try {
+ assertFalse(System.putFloat(cr, FLOAT_FIELD, 0.1f));
+ fail("Should throw");
+ } catch (IllegalArgumentException e) {
+ }
+ try {
+ assertFalse(System.putFloat(cr, FLOAT_FIELD, 30.0f));
+ fail("Should throw");
+ } catch (IllegalArgumentException e) {
+ }
+ assertEquals(1.15f, System.getFloat(cr, FLOAT_FIELD), 0.001);
+ } finally {
+ assertTrue(System.putString(cr, FLOAT_FIELD, originalFloatValue));
}
- try {
- assertFalse(System.putFloat(cr, FLOAT_FIELD, -1f));
- fail("Should throw");
- } catch (IllegalArgumentException e) {
- }
- try {
- assertFalse(System.putFloat(cr, FLOAT_FIELD, 0.1f));
- fail("Should throw");
- } catch (IllegalArgumentException e) {
- }
- try {
- assertFalse(System.putFloat(cr, FLOAT_FIELD, 30.0f));
- fail("Should throw");
- } catch (IllegalArgumentException e) {
- }
- assertEquals(1.15f, System.getFloat(cr, FLOAT_FIELD), 0.001);
}
@Test
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java b/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java
index c069760..9ee194e 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java
@@ -119,9 +119,8 @@
byte[] srcData = new byte[w * h * 4];
byte[] dstData = new byte[w * h * 4];
byte[] resultData = new byte[w * h * 4];
- Script.LaunchOptions opt = new Script.LaunchOptions();
- // unclipped but with options
- for (int i = 0; i < 28; i++) {
+
+ for (int i = 0; i < 14; i++) {
buildSrc(srcData, w, h);
buildDst(dstData, w, h);
src.copyFromUnchecked(srcData);
@@ -170,51 +169,71 @@
case 13:
mBlend.forEachMultiply(src, dst);
break;
- case 14:
+ }
+ dst.copyTo(resultData);
+ String name = javaBlend(i, srcData, dstData, 0, w, 0, h, w);
+ assertTrue(name, similar(resultData,dstData));
+ Log.v("BlendUnit", name + " " + similar(resultData, dstData));
+ }
+
+ // Do the same but passing LaunchOptions
+ int xStart = 10;
+ int xEnd = 20;
+ int yStart = 3;
+ int yEnd = 6;
+ Script.LaunchOptions opt = new Script.LaunchOptions();
+ opt.setX(xStart, xEnd).setY(yStart, yEnd);
+ for (int i = 0; i < 14; i++) {
+ buildSrc(srcData, w, h);
+ buildDst(dstData, w, h);
+ src.copyFromUnchecked(srcData);
+ dst.copyFromUnchecked(dstData);
+ switch (i) {
+ case 0:
mBlend.forEachSrc(src, dst, opt);
break;
- case 15:
+ case 1:
mBlend.forEachDst(src, dst, opt);
break;
- case 16:
+ case 2:
mBlend.forEachSrcOver(src, dst, opt);
break;
- case 17:
+ case 3:
mBlend.forEachDstOver(src, dst, opt);
break;
- case 18:
+ case 4:
mBlend.forEachSrcIn(src, dst, opt);
break;
- case 19:
+ case 5:
mBlend.forEachDstIn(src, dst, opt);
break;
- case 20:
+ case 6:
mBlend.forEachSrcOut(src, dst, opt);
break;
- case 21:
+ case 7:
mBlend.forEachDstOut(src, dst, opt);
break;
- case 22:
+ case 8:
mBlend.forEachSrcAtop(src, dst, opt);
break;
- case 23:
+ case 9:
mBlend.forEachDstAtop(src, dst, opt);
break;
- case 24:
+ case 10:
mBlend.forEachXor(src, dst, opt);
break;
- case 25:
+ case 11:
mBlend.forEachAdd(src, dst, opt);
break;
- case 26:
+ case 12:
mBlend.forEachSubtract(src, dst, opt);
break;
- case 27:
+ case 13:
mBlend.forEachMultiply(src, dst, opt);
break;
}
dst.copyTo(resultData);
- String name = javaBlend(i%14, srcData, dstData);
+ String name = javaBlend(i, srcData, dstData, xStart, xEnd, yStart, yEnd, w);
assertTrue(name, similar(resultData,dstData));
Log.v("BlendUnit", name + " " + similar(resultData, dstData));
@@ -260,6 +279,18 @@
srcData[i * 4 + 2] = (byte) 0; // blue
srcData[i * 4 + 3] = (byte) y; // alpha
}
+ // Manually set a few known problematic values.
+ // These created problems for SRC_OVER, SRC_ATOP
+ srcData[0] = 230 - 256;
+ srcData[1] = 200 - 256;
+ srcData[2] = 210 - 256;
+ srcData[3] = 7;
+
+ // These created problems for DST_OVER, DST_ATOP,
+ srcData[4] = 230 - 255;
+ srcData[5] = 200 - 256;
+ srcData[6] = 210 - 256;
+ srcData[7] = 245 - 256;
}
// Build a test pattern to be the destination pattern designed to provide a wide range of values
@@ -273,18 +304,29 @@
dstData[i * 4 + 2] = (byte) y; // blue
dstData[i * 4 + 3] = (byte) x; // alpha
}
+ // Manually set a few known problematic values
+ dstData[0] = 170 - 256;
+ dstData[1] = 180 - 256;
+ dstData[2] = 230 - 256;
+ dstData[3] = 245 - 256;
+ dstData[4] = 170 - 256;
+ dstData[5] = 180 - 256;
+ dstData[6] = 230 - 256;
+ dstData[7] = 9;
}
- public String javaBlend(int type, byte[] src, byte[] dst) {
-
- for (int i = 0; i < dst.length; i += 4) {
- byte[] rgba = func[type].filter(src[i], src[i + 1], src[i + 2], src[i + 3],
- dst[i], dst[i + 1], dst[i + 2], dst[i + 3]);
- dst[i] = rgba[0];
- dst[i + 1] = rgba[1];
- dst[i + 2] = rgba[2];
- dst[i + 3] = rgba[3];
+ public String javaBlend(int type, byte[] src, byte[] dst, int xStart, int xEnd, int yStart, int yEnd, int width) {
+ for (int y = yStart; y < yEnd; y++) {
+ for (int x = xStart; x < xEnd; x++) {
+ int i = (y * width + x) * 4;
+ byte[] rgba = func[type].filter(src[i], src[i + 1], src[i + 2], src[i + 3],
+ dst[i], dst[i + 1], dst[i + 2], dst[i + 3]);
+ dst[i] = rgba[0];
+ dst[i + 1] = rgba[1];
+ dst[i + 2] = rgba[2];
+ dst[i + 3] = rgba[3];
+ }
}
return func[type].name;
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicResize.java b/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicResize.java
index e542f37..6639757 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicResize.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicResize.java
@@ -24,7 +24,7 @@
static final int inX = 307;
static final int inY = 157;
- private void testResize(int w, int h, Element.DataType dt, int vecSize, float scaleX, float scaleY) {
+ private void testResize(int w, int h, Element.DataType dt, int vecSize, float scaleX, float scaleY, boolean useOpt) {
Element e = makeElement(dt, vecSize);
@@ -42,9 +42,14 @@
mAllocRef = makeAllocation(outW, outH, e);
mAllocDst = makeAllocation(outW, outH, e);
+ Script.LaunchOptions options = makeClipper(10, 8, 40, 46);
ScriptIntrinsicResize si = ScriptIntrinsicResize.create(mRS);
si.setInput(mAllocSrc);
- si.forEach_bicubic(mAllocRef);
+ if (useOpt) {
+ si.forEach_bicubic(mAllocRef, options);
+ } else {
+ si.forEach_bicubic(mAllocRef);
+ }
ScriptC_intrinsic_resize sr = new ScriptC_intrinsic_resize(mRS);
sr.set_scaleX((float)w/outW);
@@ -52,44 +57,77 @@
sr.set_gIn(mAllocSrc);
sr.set_gWidthIn(w);
sr.set_gHeightIn(h);
- if (dt == Element.DataType.UNSIGNED_8) {
+ if (useOpt) {
+ if (dt == Element.DataType.UNSIGNED_8) {
switch(vecSize) {
- case 4:
+ case 4:
+ sr.forEach_bicubic_U4(mAllocDst, options);
+ break;
+ case 3:
+ sr.forEach_bicubic_U3(mAllocDst, options);
+ break;
+ case 2:
+ sr.forEach_bicubic_U2(mAllocDst, options);
+ break;
+ case 1:
+ sr.forEach_bicubic_U1(mAllocDst, options);
+ break;
+ }
+ } else {
+ switch(vecSize) {
+ case 4:
+ sr.forEach_bicubic_F4(mAllocDst, options);
+ break;
+ case 3:
+ sr.forEach_bicubic_F3(mAllocDst, options);
+ break;
+ case 2:
+ sr.forEach_bicubic_F2(mAllocDst, options);
+ break;
+ case 1:
+ sr.forEach_bicubic_F1(mAllocDst, options);
+ break;
+ }
+ }
+ } else {
+ if (dt == Element.DataType.UNSIGNED_8) {
+ switch(vecSize) {
+ case 4:
sr.forEach_bicubic_U4(mAllocDst);
break;
- case 3:
+ case 3:
sr.forEach_bicubic_U3(mAllocDst);
break;
- case 2:
+ case 2:
sr.forEach_bicubic_U2(mAllocDst);
break;
- case 1:
+ case 1:
sr.forEach_bicubic_U1(mAllocDst);
break;
}
- } else {
+ } else {
switch(vecSize) {
- case 4:
+ case 4:
sr.forEach_bicubic_F4(mAllocDst);
break;
- case 3:
+ case 3:
sr.forEach_bicubic_F3(mAllocDst);
break;
- case 2:
+ case 2:
sr.forEach_bicubic_F2(mAllocDst);
break;
- case 1:
+ case 1:
sr.forEach_bicubic_F1(mAllocDst);
break;
}
+ }
}
-
mVerify.set_gAllowedIntError(1);
mVerify.invoke_verify(mAllocRef, mAllocDst, mAllocSrc);
- if (outW == w && outH == h) {
+ //when scale = 1 and we're copyin the entire input, check with the original.
+ if (outW == w && outH == h && !useOpt) {
mVerify.set_gAllowedIntError(0);
- //when scale = 1, check with the original.
mVerify.invoke_verify(mAllocRef, mAllocSrc, mAllocSrc);
mVerify.invoke_verify(mAllocDst, mAllocSrc, mAllocSrc);
}
@@ -101,343 +139,684 @@
public void test_U8_4_SCALE10_10_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f, false);
checkError();
}
public void test_U8_3_SCALE10_10_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f, false);
checkError();
}
public void test_U8_2_SCALE10_10_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f, false);
checkError();
}
public void test_U8_1_SCALE10_10_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f, false);
checkError();
}
public void test_U8_4_SCALE20_20_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f, false);
checkError();
}
public void test_U8_3_SCALE20_20_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f, false);
checkError();
}
public void test_U8_2_SCALE20_20_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f, false);
checkError();
}
public void test_U8_1_SCALE20_20_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f, false);
checkError();
}
public void test_U8_4_SCALE05_20_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f, false);
checkError();
}
public void test_U8_3_SCALE05_20_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f, false);
checkError();
}
public void test_U8_2_SCALE05_20_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f, false);
checkError();
}
public void test_U8_1_SCALE05_20_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f, false);
checkError();
}
public void test_U8_4_SCALE20_05_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f, false);
checkError();
}
public void test_U8_3_SCALE20_05_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f, false);
checkError();
}
public void test_U8_2_SCALE20_05_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f, false);
checkError();
}
public void test_U8_1_SCALE20_05_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f, false);
checkError();
}
public void test_U8_4_SCALE05_05_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f, false);
checkError();
}
public void test_U8_3_SCALE05_05_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f, false);
checkError();
}
public void test_U8_2_SCALE05_05_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f, false);
checkError();
}
public void test_U8_1_SCALE05_05_inSquare() {
- testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f);
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f, false);
checkError();
}
public void test_U8_4_SCALE10_10_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f, false);
checkError();
}
public void test_U8_3_SCALE10_10_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f, false);
checkError();
}
public void test_U8_2_SCALE10_10_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f, false);
checkError();
}
public void test_U8_1_SCALE10_10_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f, false);
checkError();
}
public void test_U8_4_SCALE20_20_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f, false);
checkError();
}
public void test_U8_3_SCALE20_20_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f, false);
checkError();
}
public void test_U8_2_SCALE20_20_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f, false);
checkError();
}
public void test_U8_1_SCALE20_20_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f, false);
checkError();
}
public void test_U8_4_SCALE05_20_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f, false);
checkError();
}
public void test_U8_3_SCALE05_20_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f, false);
checkError();
}
public void test_U8_2_SCALE05_20_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f, false);
checkError();
}
public void test_U8_1_SCALE05_20_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f, false);
checkError();
}
public void test_U8_4_SCALE20_05_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f, false);
checkError();
}
public void test_U8_3_SCALE20_05_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f, false);
checkError();
}
public void test_U8_2_SCALE20_05_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f, false);
checkError();
}
public void test_U8_1_SCALE20_05_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f, false);
checkError();
}
public void test_U8_4_SCALE05_05_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f, false);
checkError();
}
public void test_U8_3_SCALE05_05_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f, false);
checkError();
}
public void test_U8_2_SCALE05_05_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f, false);
checkError();
}
public void test_U8_1_SCALE05_05_inRectangle() {
- testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f);
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f, false);
checkError();
}
public void test_F32_4_SCALE10_10_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 4, 1.f, 1.f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 4, 1.f, 1.f, false);
checkError();
}
public void test_F32_3_SCALE10_10_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 3, 1.f, 1.f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 3, 1.f, 1.f, false);
checkError();
}
public void test_F32_2_SCALE10_10_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 2, 1.f, 1.f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 2, 1.f, 1.f, false);
checkError();
}
public void test_F32_1_SCALE10_10_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 1, 1.f, 1.f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 1, 1.f, 1.f, false);
checkError();
}
public void test_F32_4_SCALE20_20_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 2.f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 2.f, false);
checkError();
}
public void test_F32_3_SCALE20_20_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 2.f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 2.f, false);
checkError();
}
public void test_F32_2_SCALE20_20_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 2.f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 2.f, false);
checkError();
}
public void test_F32_1_SCALE20_20_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 2.f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 2.f, false);
checkError();
}
public void test_F32_4_SCALE05_20_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 2.f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 2.f, false);
checkError();
}
public void test_F32_3_SCALE05_20_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 2.f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 2.f, false);
checkError();
}
public void test_F32_2_SCALE05_20_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 2.f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 2.f, false);
checkError();
}
public void test_F32_1_SCALE05_20_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 2.f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 2.f, false);
checkError();
}
public void test_F32_4_SCALE20_05_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 0.5f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 0.5f, false);
checkError();
}
public void test_F32_3_SCALE20_05_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 0.5f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 0.5f, false);
checkError();
}
public void test_F32_2_SCALE20_05_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 0.5f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 0.5f, false);
checkError();
}
public void test_F32_1_SCALE20_05_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 0.5f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 0.5f, false);
checkError();
}
public void test_F32_4_SCALE05_05_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f, false);
checkError();
}
public void test_F32_3_SCALE05_05_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f, false);
checkError();
}
public void test_F32_2_SCALE05_05_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f, false);
checkError();
}
public void test_F32_1_SCALE05_05_inSquare() {
- testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f);
+ testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f, false);
checkError();
}
public void test_F32_4_SCALE10_10_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 4, 1.f, 1.f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 4, 1.f, 1.f, false);
checkError();
}
public void test_F32_3_SCALE10_10_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 3, 1.f, 1.f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 3, 1.f, 1.f, false);
checkError();
}
public void test_F32_2_SCALE10_10_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 2, 1.f, 1.f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 2, 1.f, 1.f, false);
checkError();
}
public void test_F32_1_SCALE10_10_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 1, 1.f, 1.f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 1, 1.f, 1.f, false);
checkError();
}
public void test_F32_4_SCALE20_20_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 2.f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 2.f, false);
checkError();
}
public void test_F32_3_SCALE20_20_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 2.f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 2.f, false);
checkError();
}
public void test_F32_2_SCALE20_20_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 2.f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 2.f, false);
checkError();
}
public void test_F32_1_SCALE20_20_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 2.f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 2.f, false);
checkError();
}
public void test_F32_4_SCALE05_20_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 2.f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 2.f, false);
checkError();
}
public void test_F32_3_SCALE05_20_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 2.f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 2.f, false);
checkError();
}
public void test_F32_2_SCALE05_20_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 2.f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 2.f, false);
checkError();
}
public void test_F32_1_SCALE05_20_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 2.f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 2.f, false);
checkError();
}
public void test_F32_4_SCALE20_05_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 0.5f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 0.5f, false);
checkError();
}
public void test_F32_3_SCALE20_05_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 0.5f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 0.5f, false);
checkError();
}
public void test_F32_2_SCALE20_05_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 0.5f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 0.5f, false);
checkError();
}
public void test_F32_1_SCALE20_05_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 0.5f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 0.5f, false);
checkError();
}
public void test_F32_4_SCALE05_05_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f, false);
checkError();
}
public void test_F32_3_SCALE05_05_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f, false);
checkError();
}
public void test_F32_2_SCALE05_05_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f, false);
checkError();
}
public void test_F32_1_SCALE05_05_inRectangle() {
- testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f);
+ testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f, false);
+ checkError();
+ }
+
+ public void test_U8_4_SCALE10_10_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f, true);
+ checkError();
+ }
+ public void test_U8_3_SCALE10_10_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f, true);
+ checkError();
+ }
+ public void test_U8_2_SCALE10_10_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f, true);
+ checkError();
+ }
+ public void test_U8_1_SCALE10_10_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f, true);
+ checkError();
+ }
+
+ public void test_U8_4_SCALE20_20_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f, true);
+ checkError();
+ }
+ public void test_U8_3_SCALE20_20_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f, true);
+ checkError();
+ }
+ public void test_U8_2_SCALE20_20_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f, true);
+ checkError();
+ }
+ public void test_U8_1_SCALE20_20_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f, true);
+ checkError();
+ }
+
+ public void test_U8_4_SCALE05_20_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f, true);
+ checkError();
+ }
+ public void test_U8_3_SCALE05_20_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f, true);
+ checkError();
+ }
+ public void test_U8_2_SCALE05_20_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f, true);
+ checkError();
+ }
+ public void test_U8_1_SCALE05_20_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f, true);
+ checkError();
+ }
+
+ public void test_U8_4_SCALE20_05_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f, true);
+ checkError();
+ }
+ public void test_U8_3_SCALE20_05_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f, true);
+ checkError();
+ }
+ public void test_U8_2_SCALE20_05_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f, true);
+ checkError();
+ }
+ public void test_U8_1_SCALE20_05_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f, true);
+ checkError();
+ }
+
+ public void test_U8_4_SCALE05_05_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f, true);
+ checkError();
+ }
+ public void test_U8_3_SCALE05_05_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f, true);
+ checkError();
+ }
+ public void test_U8_2_SCALE05_05_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f, true);
+ checkError();
+ }
+ public void test_U8_1_SCALE05_05_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f, true);
+ checkError();
+ }
+
+ public void test_U8_4_SCALE10_10_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f, true);
+ checkError();
+ }
+ public void test_U8_3_SCALE10_10_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f, true);
+ checkError();
+ }
+ public void test_U8_2_SCALE10_10_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f, true);
+ checkError();
+ }
+ public void test_U8_1_SCALE10_10_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f, true);
+ checkError();
+ }
+
+ public void test_U8_4_SCALE20_20_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f, true);
+ checkError();
+ }
+ public void test_U8_3_SCALE20_20_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f, true);
+ checkError();
+ }
+ public void test_U8_2_SCALE20_20_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f, true);
+ checkError();
+ }
+ public void test_U8_1_SCALE20_20_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f, true);
+ checkError();
+ }
+
+ public void test_U8_4_SCALE05_20_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f, true);
+ checkError();
+ }
+ public void test_U8_3_SCALE05_20_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f, true);
+ checkError();
+ }
+ public void test_U8_2_SCALE05_20_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f, true);
+ checkError();
+ }
+ public void test_U8_1_SCALE05_20_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f, true);
+ checkError();
+ }
+
+ public void test_U8_4_SCALE20_05_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f, true);
+ checkError();
+ }
+ public void test_U8_3_SCALE20_05_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f, true);
+ checkError();
+ }
+ public void test_U8_2_SCALE20_05_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f, true);
+ checkError();
+ }
+ public void test_U8_1_SCALE20_05_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f, true);
+ checkError();
+ }
+
+ public void test_U8_4_SCALE05_05_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f, true);
+ checkError();
+ }
+ public void test_U8_3_SCALE05_05_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f, true);
+ checkError();
+ }
+ public void test_U8_2_SCALE05_05_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f, true);
+ checkError();
+ }
+ public void test_U8_1_SCALE05_05_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f, true);
+ checkError();
+ }
+
+
+ public void test_F32_4_SCALE10_10_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 4, 1.f, 1.f, true);
+ checkError();
+ }
+ public void test_F32_3_SCALE10_10_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 3, 1.f, 1.f, true);
+ checkError();
+ }
+ public void test_F32_2_SCALE10_10_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 2, 1.f, 1.f, true);
+ checkError();
+ }
+ public void test_F32_1_SCALE10_10_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 1, 1.f, 1.f, true);
+ checkError();
+ }
+
+ public void test_F32_4_SCALE20_20_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 2.f, true);
+ checkError();
+ }
+ public void test_F32_3_SCALE20_20_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 2.f, true);
+ checkError();
+ }
+ public void test_F32_2_SCALE20_20_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 2.f, true);
+ checkError();
+ }
+ public void test_F32_1_SCALE20_20_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 2.f, true);
+ checkError();
+ }
+
+ public void test_F32_4_SCALE05_20_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 2.f, true);
+ checkError();
+ }
+ public void test_F32_3_SCALE05_20_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 2.f, true);
+ checkError();
+ }
+ public void test_F32_2_SCALE05_20_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 2.f, true);
+ checkError();
+ }
+ public void test_F32_1_SCALE05_20_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 2.f, true);
+ checkError();
+ }
+
+ public void test_F32_4_SCALE20_05_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 0.5f, true);
+ checkError();
+ }
+ public void test_F32_3_SCALE20_05_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 0.5f, true);
+ checkError();
+ }
+ public void test_F32_2_SCALE20_05_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 0.5f, true);
+ checkError();
+ }
+ public void test_F32_1_SCALE20_05_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 0.5f, true);
+ checkError();
+ }
+
+ public void test_F32_4_SCALE05_05_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f, true);
+ checkError();
+ }
+ public void test_F32_3_SCALE05_05_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f, true);
+ checkError();
+ }
+ public void test_F32_2_SCALE05_05_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f, true);
+ checkError();
+ }
+ public void test_F32_1_SCALE05_05_inSquare_opt() {
+ testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f, true);
+ checkError();
+ }
+
+ public void test_F32_4_SCALE10_10_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 4, 1.f, 1.f, true);
+ checkError();
+ }
+ public void test_F32_3_SCALE10_10_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 3, 1.f, 1.f, true);
+ checkError();
+ }
+ public void test_F32_2_SCALE10_10_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 2, 1.f, 1.f, true);
+ checkError();
+ }
+ public void test_F32_1_SCALE10_10_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 1, 1.f, 1.f, true);
+ checkError();
+ }
+
+ public void test_F32_4_SCALE20_20_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 2.f, true);
+ checkError();
+ }
+ public void test_F32_3_SCALE20_20_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 2.f, true);
+ checkError();
+ }
+ public void test_F32_2_SCALE20_20_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 2.f, true);
+ checkError();
+ }
+ public void test_F32_1_SCALE20_20_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 2.f, true);
+ checkError();
+ }
+
+ public void test_F32_4_SCALE05_20_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 2.f, true);
+ checkError();
+ }
+ public void test_F32_3_SCALE05_20_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 2.f, true);
+ checkError();
+ }
+ public void test_F32_2_SCALE05_20_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 2.f, true);
+ checkError();
+ }
+ public void test_F32_1_SCALE05_20_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 2.f, true);
+ checkError();
+ }
+
+ public void test_F32_4_SCALE20_05_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 0.5f, true);
+ checkError();
+ }
+ public void test_F32_3_SCALE20_05_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 0.5f, true);
+ checkError();
+ }
+ public void test_F32_2_SCALE20_05_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 0.5f, true);
+ checkError();
+ }
+ public void test_F32_1_SCALE20_05_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 0.5f, true);
+ checkError();
+ }
+
+ public void test_F32_4_SCALE05_05_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f, true);
+ checkError();
+ }
+ public void test_F32_3_SCALE05_05_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f, true);
+ checkError();
+ }
+ public void test_F32_2_SCALE05_05_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f, true);
+ checkError();
+ }
+ public void test_F32_1_SCALE05_05_inRectangle_opt() {
+ testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f, true);
checkError();
}
diff --git a/tests/tests/resourcesloader/OWNERS b/tests/tests/resourcesloader/OWNERS
index 0bccde9..5174db8 100644
--- a/tests/tests/resourcesloader/OWNERS
+++ b/tests/tests/resourcesloader/OWNERS
@@ -1,5 +1,5 @@
# Bug component: 568761
-rtmitchell@google.com
+zyy@google.com
chiuwinson@google.com
toddke@google.com
patb@google.com
diff --git a/tests/tests/role/Android.bp b/tests/tests/role/Android.bp
index a638561..ae1fdfd 100644
--- a/tests/tests/role/Android.bp
+++ b/tests/tests/role/Android.bp
@@ -43,5 +43,6 @@
data: [
":CtsRoleTestApp",
":CtsRoleTestApp28",
+ ":CtsRoleTestApp33WithoutInCallService",
],
}
diff --git a/tests/tests/role/AndroidTest.xml b/tests/tests/role/AndroidTest.xml
index 4a8189f..1794e8b 100644
--- a/tests/tests/role/AndroidTest.xml
+++ b/tests/tests/role/AndroidTest.xml
@@ -20,6 +20,7 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
@@ -38,6 +39,7 @@
<option name="cleanup" value="true" />
<option name="push" value="CtsRoleTestApp.apk->/data/local/tmp/cts/role/CtsRoleTestApp.apk" />
<option name="push" value="CtsRoleTestApp28.apk->/data/local/tmp/cts/role/CtsRoleTestApp28.apk" />
+ <option name="push" value="CtsRoleTestApp33WithoutInCallService.apk->/data/local/tmp/cts/role/CtsRoleTestApp33WithoutInCallService.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml b/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
index eb17122..b2dfca9 100644
--- a/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
+++ b/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
@@ -54,7 +54,20 @@
<data android:scheme="tel" />
</intent-filter>
</activity>
-
+ <service
+ android:name=".DialerInCallService"
+ android:permission="android.permission.BIND_INCALL_SERVICE"
+ android:exported="true">
+ <meta-data
+ android:name="android.telecom.IN_CALL_SERVICE_UI"
+ android:value="true"/>
+ <meta-data
+ android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"
+ android:value="false"/>
+ <intent-filter>
+ <action android:name="android.telecom.InCallService" />
+ </intent-filter>
+ </service>
<!-- Sms -->
<activity
android:name=".SmsSendToActivity"
diff --git a/tests/tests/role/CtsRoleTestApp33WithoutInCallService/Android.bp b/tests/tests/role/CtsRoleTestApp33WithoutInCallService/Android.bp
new file mode 100644
index 0000000..7cce565
--- /dev/null
+++ b/tests/tests/role/CtsRoleTestApp33WithoutInCallService/Android.bp
@@ -0,0 +1,23 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsRoleTestApp33WithoutInCallService",
+ min_sdk_version: "30",
+ target_sdk_version: "33",
+}
diff --git a/tests/tests/role/CtsRoleTestApp33WithoutInCallService/AndroidManifest.xml b/tests/tests/role/CtsRoleTestApp33WithoutInCallService/AndroidManifest.xml
new file mode 100644
index 0000000..a6504ad
--- /dev/null
+++ b/tests/tests/role/CtsRoleTestApp33WithoutInCallService/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.app.role.cts.app33WithoutInCallService">
+ <application android:label="CtsRoleTestApp33WithoutInCallService">
+ <!-- Dialer -->
+ <activity
+ android:name=".DialerDialActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="tel" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
index 766a927..4cc198c 100644
--- a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
+++ b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
@@ -111,6 +111,11 @@
private static final String APP_28_CHANGE_DEFAULT_SMS_ACTIVITY_NAME = APP_28_PACKAGE_NAME
+ ".ChangeDefaultSmsActivity";
+ private static final String APP_33_WITHOUT_INCALLSERVICE_APK_PATH =
+ "/data/local/tmp/cts/role/CtsRoleTestApp33WithoutInCallService.apk";
+ private static final String APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME =
+ "android.app.role.cts.app33WithoutInCallService";
+
private static final String PERMISSION_MANAGE_ROLES_FROM_CONTROLLER =
"com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER";
@@ -158,12 +163,14 @@
public void installApp() throws Exception {
installPackage(APP_APK_PATH);
installPackage(APP_28_APK_PATH);
+ installPackage(APP_33_WITHOUT_INCALLSERVICE_APK_PATH);
}
@After
public void uninstallApp() throws Exception {
uninstallPackage(APP_PACKAGE_NAME);
uninstallPackage(APP_28_PACKAGE_NAME);
+ uninstallPackage(APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME);
}
@Before
@@ -537,6 +544,28 @@
}
@Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ public void testHoldDialerRoleRequirementWithInCallServiceAndSdk()
+ throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
+ // target below sdk 33 without InCallService component can hold dialer role
+ addRoleHolder(
+ RoleManager.ROLE_DIALER, APP_28_PACKAGE_NAME, true);
+ assertIsRoleHolder(
+ RoleManager.ROLE_DIALER, APP_28_PACKAGE_NAME, true);
+ // target sdk 33 without InCallService component cannot hold dialer role
+ addRoleHolder(
+ RoleManager.ROLE_DIALER, APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME, false);
+ assertIsRoleHolder(
+ RoleManager.ROLE_DIALER, APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME, false);
+ // target sdk 33 with InCallService component can hold dialer role
+ addRoleHolder(
+ RoleManager.ROLE_DIALER, APP_PACKAGE_NAME, true);
+ assertIsRoleHolder(
+ RoleManager.ROLE_DIALER, APP_PACKAGE_NAME, true);
+ }
+
+ @Test
public void
targetSdk28AndChangeDefaultSmsForAnotherAppAsHolderAndAllowThenTheOtherAppIsDefaultSms()
throws Exception {
diff --git a/tests/tests/safetycenter/Android.bp b/tests/tests/safetycenter/Android.bp
new file mode 100644
index 0000000..dddd03c
--- /dev/null
+++ b/tests/tests/safetycenter/Android.bp
@@ -0,0 +1,47 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsSafetyCenterTestCases",
+ defaults: ["mts-target-sdk-version-current"],
+ sdk_version: "test_current",
+ min_sdk_version: "30",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "androidx.test.uiautomator_uiautomator",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "kotlin-stdlib",
+ "kotlin-test",
+ "modules-utils-build_system",
+ "truth-prebuilt",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/tests/safetycenter/AndroidManifest.xml b/tests/tests/safetycenter/AndroidManifest.xml
new file mode 100644
index 0000000..c457c69
--- /dev/null
+++ b/tests/tests/safetycenter/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.safetycenter.cts">
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ </application>
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests for SafetyCenter"
+ android:targetPackage="android.safetycenter.cts">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener"/>
+ </instrumentation>
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+</manifest>
diff --git a/tests/tests/safetycenter/AndroidTest.xml b/tests/tests/safetycenter/AndroidTest.xml
new file mode 100644
index 0000000..fc74c4a
--- /dev/null
+++ b/tests/tests/safetycenter/AndroidTest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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 SafetyCenter test cases">
+
+ <!-- TODO(b/207111503): Use Sdk33ModuleController once available, and remove @SdkSuppress
+ annotations -->
+ <object
+ class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController"
+ type="module_controller"/>
+
+ <option name="config-descriptor:metadata" key="component" value="framework"/>
+ <option name="config-descriptor:metadata" key="parameter"
+ value="not_instant_app"/>
+ <option name="config-descriptor:metadata" key="parameter"
+ value="not_multi_abi"/>
+ <option name="config-descriptor:metadata" key="parameter"
+ value="secondary_user"/>
+
+ <option name="test-suite-tag" value="cts"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="force-skip-system-props"
+ value="true"/> <!-- avoid restarting device -->
+ </target_preparer>
+
+ <target_preparer
+ class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="CtsSafetyCenterTestCases.apk"/>
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.safetycenter.cts"/>
+ <option name="runtime-hint" value="5m"/>
+ </test>
+</configuration>
diff --git a/tests/tests/safetycenter/OWNERS b/tests/tests/safetycenter/OWNERS
new file mode 100644
index 0000000..026d342
--- /dev/null
+++ b/tests/tests/safetycenter/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1026964
+
+include platform/frameworks/base:/core/java/android/permission/OWNERS
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterActivityTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterActivityTest.kt
new file mode 100644
index 0000000..0adf458
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterActivityTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts
+
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.ACTION_SAFETY_CENTER
+import android.os.Build.VERSION_CODES.TIRAMISU
+import android.support.test.uiautomator.By
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = TIRAMISU, codeName = "Tiramisu")
+class SafetyCenterActivityTest {
+ private val context: Context = getApplicationContext()
+
+ @Test
+ fun launchActivity_showsSecurityAndPrivacyTitle() {
+ context.startActivity(
+ Intent(ACTION_SAFETY_CENTER).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ )
+
+ // CollapsingToolbar title can't be found by text, so using description instead.
+ waitFindObject(By.desc("Security & Privacy"))
+ }
+}
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt
new file mode 100644
index 0000000..4448967
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Parcel
+import android.safetycenter.SafetyCenterData
+import android.safetycenter.SafetyCenterEntry
+import android.safetycenter.SafetyCenterEntryGroup
+import android.safetycenter.SafetyCenterEntryOrGroup
+import android.safetycenter.SafetyCenterIssue
+import android.safetycenter.SafetyCenterStatus
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SafetyCenterDataTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val pendingIntent = PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ Intent("Fake Data"),
+ PendingIntent.FLAG_IMMUTABLE)
+
+ val status1 = SafetyCenterStatus.Builder()
+ .setTitle("This is my title")
+ .setSummary("This is my summary")
+ .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION)
+ .build()
+ val status2 = SafetyCenterStatus.Builder()
+ .setTitle("This is also my title")
+ .setSummary("This is also my summary")
+ .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK)
+ .build()
+
+ val issue1 = SafetyCenterIssue.Builder("iSsUe_iD_oNe")
+ .setTitle("An issue title")
+ .setSummary("An issue summary")
+ .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
+ .build()
+ val issue2 = SafetyCenterIssue.Builder("iSsUe_iD_tWo")
+ .setTitle("Another issue title")
+ .setSummary("Another issue summary")
+ .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATION)
+ .build()
+
+ val entry1 = SafetyCenterEntry.Builder("eNtRy_iD_OnE")
+ .setTitle("An entry title")
+ .setPendingIntent(pendingIntent)
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+ .build()
+ val entry2 = SafetyCenterEntry.Builder("eNtRy_iD_TwO")
+ .setTitle("Another entry title")
+ .setPendingIntent(pendingIntent)
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+ .build()
+
+ val entryGroup1 = SafetyCenterEntryGroup.Builder("eNtRy_gRoUp_iD")
+ .setTitle("An entry group title")
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+ .setEntries(listOf(entry2))
+ .build()
+
+ val entryOrGroup1 = SafetyCenterEntryOrGroup(entry1)
+ val entryOrGroup2 = SafetyCenterEntryOrGroup(entryGroup1)
+
+ val data1 = SafetyCenterData(status1, listOf(issue1), listOf(entryOrGroup1))
+ val data2 = SafetyCenterData(status2, listOf(issue2), listOf(entryOrGroup2))
+
+ @Test
+ fun getStatus_returnsStatus() {
+ assertThat(data1.status).isEqualTo(status1)
+ assertThat(data2.status).isEqualTo(status2)
+ }
+
+ @Test
+ fun getIssues_returnsIssues() {
+ assertThat(data1.issues).containsExactly(issue1)
+ assertThat(data2.issues).containsExactly(issue2)
+ }
+
+ @Test
+ fun getEntriesOrGroups_returnsEntriesOrGroups() {
+ assertThat(data1.entriesOrGroups).containsExactly(entryOrGroup1)
+ assertThat(data2.entriesOrGroups).containsExactly(entryOrGroup2)
+ }
+
+ @Test
+ fun describeContents_returns0() {
+ assertThat(data1.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ fun createFromParcel_withWriteToParcel_returnsEquivalentObject() {
+ val parcel: Parcel = Parcel.obtain()
+
+ data1.writeToParcel(parcel, 0 /* flags */)
+ parcel.setDataPosition(0)
+ val fromParcel = SafetyCenterData.CREATOR.createFromParcel(parcel)
+ parcel.recycle()
+
+ assertThat(fromParcel).isEqualTo(data1)
+ }
+
+ @Test
+ fun equals_hashCode_toString_equalByReference_areEqual() {
+ assertThat(data1).isEqualTo(data1)
+ assertThat(data1.hashCode()).isEqualTo(data1.hashCode())
+ assertThat(data1.toString()).isEqualTo(data1.toString())
+ }
+
+ @Test
+ fun equals_hashCode_toString_equalByValue_areEqual() {
+ val data = SafetyCenterData(status1, listOf(issue1), listOf(entryOrGroup1))
+ val equivalentData = SafetyCenterData(status1, listOf(issue1), listOf(entryOrGroup1))
+
+ assertThat(data).isEqualTo(equivalentData)
+ assertThat(data.hashCode()).isEqualTo(equivalentData.hashCode())
+ assertThat(data.toString()).isEqualTo(equivalentData.toString())
+ }
+
+ @Test
+ fun equals_hashCode_toString_withEmptyLists_equalByValue_areEqual() {
+ val data = SafetyCenterData(status1, listOf(), listOf())
+ val equivalentData = SafetyCenterData(status1, listOf(), listOf())
+
+ assertThat(data).isEqualTo(equivalentData)
+ assertThat(data.hashCode()).isEqualTo(equivalentData.hashCode())
+ assertThat(data.toString()).isEqualTo(equivalentData.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentStatuses_areNotEqual() {
+ val data = SafetyCenterData(status1, listOf(issue1), listOf(entryOrGroup1))
+ val differentData = SafetyCenterData(status2, listOf(issue1), listOf(entryOrGroup1))
+
+ assertThat(data).isNotEqualTo(differentData)
+ assertThat(data.toString()).isNotEqualTo(differentData.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentIssues_areNotEqual() {
+ val data = SafetyCenterData(status1, listOf(issue1), listOf(entryOrGroup1))
+ val differentData = SafetyCenterData(status1, listOf(issue2), listOf(entryOrGroup1))
+
+ assertThat(data).isNotEqualTo(differentData)
+ assertThat(data.toString()).isNotEqualTo(differentData.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentEntriesOrGroups_areNotEqual() {
+ val data = SafetyCenterData(status1, listOf(issue1), listOf(entryOrGroup1))
+ val differentData = SafetyCenterData(status1, listOf(issue1), listOf(entryOrGroup2))
+
+ assertThat(data).isNotEqualTo(differentData)
+ assertThat(data.toString()).isNotEqualTo(differentData.toString())
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryGroupTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryGroupTest.kt
new file mode 100644
index 0000000..ae6ed3f
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryGroupTest.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Parcel
+import android.safetycenter.SafetyCenterEntry
+import android.safetycenter.SafetyCenterEntryGroup
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SafetyCenterEntryGroupTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val pendingIntent = PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ Intent("Fake Data"),
+ PendingIntent.FLAG_IMMUTABLE)
+
+ val entry1 = SafetyCenterEntry.Builder("eNtRy_iD_OnE")
+ .setTitle("An entry title")
+ .setPendingIntent(pendingIntent)
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+ .build()
+ val entry2 = SafetyCenterEntry.Builder("eNtRy_iD_TwO")
+ .setTitle("Another entry title")
+ .setPendingIntent(pendingIntent)
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+ .build()
+
+ val groupId1 = "gRoUp_iD_oNe"
+ val groupId2 = "gRoUp_iD_tWo"
+
+ val entryGroup1 = SafetyCenterEntryGroup.Builder(groupId1)
+ .setTitle("A group title")
+ .setSummary("A group summary")
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+ .setEntries(listOf(entry1))
+ .build()
+ val entryGroup2 = SafetyCenterEntryGroup.Builder(groupId2)
+ .setTitle("Another group title")
+ .setSummary("Another group summary")
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+ .setEntries(listOf(entry2))
+ .build()
+
+ @Test
+ fun getId_returnsId() {
+ assertThat(entryGroup1.id).isEqualTo(groupId1)
+ assertThat(entryGroup2.id).isEqualTo(groupId2)
+ }
+
+ @Test
+ fun getTitle_returnsTitle() {
+ assertThat(SafetyCenterEntryGroup.Builder(entryGroup1).setTitle("title one").build().title)
+ .isEqualTo("title one")
+ assertThat(SafetyCenterEntryGroup.Builder(entryGroup1).setTitle("title two").build().title)
+ .isEqualTo("title two")
+ }
+
+ @Test
+ fun getSummary_returnsSummary() {
+ assertThat(SafetyCenterEntryGroup.Builder(entryGroup1).setSummary("one").build().summary)
+ .isEqualTo("one")
+ assertThat(SafetyCenterEntryGroup.Builder(entryGroup1).setSummary("two").build().summary)
+ .isEqualTo("two")
+ assertThat(SafetyCenterEntryGroup.Builder(entryGroup1).setSummary(null).build().summary)
+ .isNull()
+ }
+
+ @Test
+ fun getSeverityLevel_returnsSeverityLevel() {
+ assertThat(SafetyCenterEntryGroup.Builder(entryGroup1)
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+ .build()
+ .severityLevel)
+ .isEqualTo(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+ assertThat(SafetyCenterEntryGroup.Builder(entryGroup1)
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_NONE)
+ .build()
+ .severityLevel)
+ .isEqualTo(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_NONE)
+ }
+
+ @Test
+ fun getStatelessIconType_returnsStatelessIconType() {
+ assertThat(entryGroup1.statelessIconType)
+ .isEqualTo(SafetyCenterEntry.STATELESS_ICON_TYPE_NONE)
+ assertThat(SafetyCenterEntryGroup.Builder(entryGroup1)
+ .setStatelessIconType(SafetyCenterEntry.STATELESS_ICON_TYPE_PRIVACY)
+ .build()
+ .statelessIconType)
+ .isEqualTo(SafetyCenterEntry.STATELESS_ICON_TYPE_PRIVACY)
+ }
+
+ @Test
+ fun getEntries_returnsEntries() {
+ assertThat(entryGroup1.entries).containsExactly(entry1)
+ assertThat(entryGroup2.entries).containsExactly(entry2)
+ }
+
+ @Test
+ fun describeContents_returns0() {
+ assertThat(entryGroup1.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ fun createFromParcel_withWriteToParcel_returnsEquivalentObject() {
+ val parcel = Parcel.obtain()
+
+ entryGroup1.writeToParcel(parcel, /* flags= */ 0)
+ parcel.setDataPosition(0)
+
+ val fromParcel = SafetyCenterEntryGroup.CREATOR.createFromParcel(parcel)
+ parcel.recycle()
+
+ assertThat(fromParcel).isEqualTo(entryGroup1)
+ }
+
+ @Test
+ fun equals_hashCode_toString_equalByReference_areEqual() {
+ assertThat(entryGroup1).isEqualTo(entryGroup1)
+ assertThat(entryGroup1.hashCode()).isEqualTo(entryGroup1.hashCode())
+ assertThat(entryGroup1.toString()).isEqualTo(entryGroup1.toString())
+ }
+
+ @Test
+ fun equals_hashCode_toString_equalByValue_areEqual() {
+ val equivalentToEntryGroup1 = SafetyCenterEntryGroup.Builder(entryGroup1).build()
+
+ assertThat(equivalentToEntryGroup1).isEqualTo(entryGroup1)
+ assertThat(equivalentToEntryGroup1.hashCode()).isEqualTo(entryGroup1.hashCode())
+ assertThat(equivalentToEntryGroup1.toString()).isEqualTo(entryGroup1.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentIds_areNotEqual() {
+ val differentFromEntryGroup1 = SafetyCenterEntryGroup.Builder(entryGroup1)
+ .setId("different!")
+ .build()
+
+ assertThat(differentFromEntryGroup1).isNotEqualTo(entryGroup1)
+ assertThat(differentFromEntryGroup1.toString()).isNotEqualTo(entryGroup1.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentTitles_areNotEqual() {
+ val differentFromEntryGroup1 = SafetyCenterEntryGroup.Builder(entryGroup1)
+ .setTitle("different!")
+ .build()
+
+ assertThat(differentFromEntryGroup1).isNotEqualTo(entryGroup1)
+ assertThat(differentFromEntryGroup1.toString()).isNotEqualTo(entryGroup1.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentSummaries_areNotEqual() {
+ val differentFromEntryGroup1 = SafetyCenterEntryGroup.Builder(entryGroup1)
+ .setSummary("different!")
+ .build()
+
+ assertThat(differentFromEntryGroup1).isNotEqualTo(entryGroup1)
+ assertThat(differentFromEntryGroup1.toString()).isNotEqualTo(entryGroup1.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentSeverityLevels_areNotEqual() {
+ val differentFromEntryGroup1 = SafetyCenterEntryGroup.Builder(entryGroup1)
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN)
+ .build()
+
+ assertThat(differentFromEntryGroup1).isNotEqualTo(entryGroup1)
+ assertThat(differentFromEntryGroup1.toString()).isNotEqualTo(entryGroup1.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentStatelessIconTypes_areNotEqual() {
+ val differentFromEntryGroup1 = SafetyCenterEntryGroup.Builder(entryGroup1)
+ .setStatelessIconType(SafetyCenterEntry.STATELESS_ICON_TYPE_PRIVACY)
+ .build()
+
+ assertThat(differentFromEntryGroup1).isNotEqualTo(entryGroup1)
+ assertThat(differentFromEntryGroup1.toString()).isNotEqualTo(entryGroup1.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentEntries_areNotEqual() {
+ val differentFromEntryGroup1 = SafetyCenterEntryGroup.Builder(entryGroup1)
+ .setEntries(listOf(entry2))
+ .build()
+
+ assertThat(differentFromEntryGroup1).isNotEqualTo(entryGroup1)
+ assertThat(differentFromEntryGroup1.toString()).isNotEqualTo(entryGroup1.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentEntries_emptyList_areNotEqual() {
+ val differentFromEntryGroup1 = SafetyCenterEntryGroup.Builder(entryGroup1)
+ .setEntries(listOf())
+ .build()
+
+ assertThat(differentFromEntryGroup1).isNotEqualTo(entryGroup1)
+ assertThat(differentFromEntryGroup1.toString()).isNotEqualTo(entryGroup1.toString())
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryOrGroupTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryOrGroupTest.kt
new file mode 100644
index 0000000..fe69602
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryOrGroupTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Parcel
+import android.safetycenter.SafetyCenterEntry
+import android.safetycenter.SafetyCenterEntryGroup
+import android.safetycenter.SafetyCenterEntryOrGroup
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SafetyCenterEntryOrGroupTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val pendingIntent = PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ Intent("Fake Data"),
+ PendingIntent.FLAG_IMMUTABLE)
+
+ val entry1 = SafetyCenterEntry.Builder("eNtRy_iD_OnE")
+ .setTitle("An entry title")
+ .setPendingIntent(pendingIntent)
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+ .build()
+ val entry2 = SafetyCenterEntry.Builder("eNtRy_iD_TwO")
+ .setTitle("Another entry title")
+ .setPendingIntent(pendingIntent)
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+ .build()
+
+ val entryGroup1 = SafetyCenterEntryGroup.Builder("gRoUp_iD_oNe")
+ .setTitle("A group title")
+ .setSummary("A group summary")
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+ .setEntries(listOf(entry1))
+ .build()
+ val entryGroup2 = SafetyCenterEntryGroup.Builder("gRoUp_iD_tWo")
+ .setTitle("Another group title")
+ .setSummary("Another group summary")
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+ .setEntries(listOf(entry2))
+ .build()
+
+ val entryOrGroupWithEntry = SafetyCenterEntryOrGroup(entry1)
+ val entryOrGroupWithGroup = SafetyCenterEntryOrGroup(entryGroup1)
+
+ @Test
+ fun getEntry_returnsEntry() {
+ assertThat(entryOrGroupWithEntry.entry).isEqualTo(entry1)
+ }
+
+ @Test
+ fun getEntry_returnsEntry_whenNull() {
+ assertThat(entryOrGroupWithGroup.entry).isNull()
+ }
+
+ @Test
+ fun getEntryGroup_returnsEntryGroup() {
+ assertThat(entryOrGroupWithGroup.entryGroup).isEqualTo(entryGroup1)
+ }
+
+ @Test
+ fun getEntryGroup_returnsEntryGroup_whenNull() {
+ assertThat(entryOrGroupWithEntry.entryGroup).isNull()
+ }
+
+ @Test
+ fun describeContents_returns0() {
+ assertThat(entryOrGroupWithEntry.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ fun createFromParcel_withWriteToParcel_withEntry_returnsEquivalentObject() {
+ runCreateFromParcel_withWriteToParcel_withGroup_returnsEquivalentObjectTest(
+ entryOrGroupWithEntry)
+ }
+
+ @Test
+ fun createFromParcel_withWriteToParcel_withGroup_returnsEquivalentObject() {
+ runCreateFromParcel_withWriteToParcel_withGroup_returnsEquivalentObjectTest(
+ entryOrGroupWithGroup)
+ }
+
+ fun runCreateFromParcel_withWriteToParcel_withGroup_returnsEquivalentObjectTest(
+ entryOrGroup: SafetyCenterEntryOrGroup
+ ) {
+ val parcel: Parcel = Parcel.obtain()
+
+ entryOrGroup.writeToParcel(parcel, 0 /* flags */)
+ parcel.setDataPosition(0)
+ val fromParcel = SafetyCenterEntryOrGroup.CREATOR.createFromParcel(parcel)
+ parcel.recycle()
+
+ assertThat(fromParcel).isEqualTo(entryOrGroup)
+ }
+
+ @Test
+ fun equals_hashCode_toString_whenEqualByReference_areEqual() {
+ assertThat(entryOrGroupWithEntry).isEqualTo(entryOrGroupWithEntry)
+ assertThat(entryOrGroupWithEntry.hashCode()).isEqualTo(entryOrGroupWithEntry.hashCode())
+ assertThat(entryOrGroupWithEntry.toString()).isEqualTo(entryOrGroupWithEntry.toString())
+ }
+
+ @Test
+ fun equals_hashCode_toString_whenEqualByValue_withEntry_areEqual() {
+ val entryOrGroup = SafetyCenterEntryOrGroup(entry1)
+ val equivalentEntryOrGroup = SafetyCenterEntryOrGroup(entry1)
+
+ assertThat(entryOrGroup).isEqualTo(equivalentEntryOrGroup)
+ assertThat(entryOrGroup.hashCode()).isEqualTo(equivalentEntryOrGroup.hashCode())
+ assertThat(entryOrGroup.toString()).isEqualTo(equivalentEntryOrGroup.toString())
+ }
+
+ @Test
+ fun equals_hashCode_toString_whenEqualByValue_withGroup_areEqual() {
+ val entryOrGroup = SafetyCenterEntryOrGroup(entryGroup1)
+ val equivalentEntryOrGroup = SafetyCenterEntryOrGroup(entryGroup1)
+
+ assertThat(entryOrGroup).isEqualTo(equivalentEntryOrGroup)
+ assertThat(entryOrGroup.hashCode()).isEqualTo(equivalentEntryOrGroup.hashCode())
+ assertThat(entryOrGroup.toString()).isEqualTo(equivalentEntryOrGroup.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentEntryAndGroup_areNotEqual() {
+ assertThat(entryOrGroupWithEntry).isNotEqualTo(entryOrGroupWithGroup)
+ assertThat(entryOrGroupWithEntry.toString()).isNotEqualTo(entryOrGroupWithGroup.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentEntries_areNotEqual() {
+ val entryOrGroup = SafetyCenterEntryOrGroup(entry1)
+ val differentEntryOrGroup = SafetyCenterEntryOrGroup(entry2)
+
+ assertThat(entryOrGroup).isNotEqualTo(differentEntryOrGroup)
+ assertThat(entryOrGroup.toString()).isNotEqualTo(differentEntryOrGroup.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentGroups_areNotEqual() {
+ val entryOrGroup = SafetyCenterEntryOrGroup(entryGroup1)
+ val differentEntryOrGroup = SafetyCenterEntryOrGroup(entryGroup2)
+
+ assertThat(entryOrGroup).isNotEqualTo(differentEntryOrGroup)
+ assertThat(entryOrGroup.toString()).isNotEqualTo(differentEntryOrGroup.toString())
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryTest.kt
new file mode 100644
index 0000000..9d36faf
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryTest.kt
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Parcel
+import android.safetycenter.SafetyCenterEntry
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SafetyCenterEntryTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val pendingIntent1 = PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ Intent("Fake Data"),
+ PendingIntent.FLAG_IMMUTABLE)
+ private val pendingIntent2 = PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ Intent("Fake Different Data"),
+ PendingIntent.FLAG_IMMUTABLE)
+
+ private val iconAction1 = SafetyCenterEntry.IconAction(
+ SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent1)
+ private val iconAction2 = SafetyCenterEntry.IconAction(
+ SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO, pendingIntent2)
+
+ private val entry1 = SafetyCenterEntry.Builder("eNtRy_iD")
+ .setTitle("a title")
+ .setSummary("a summary")
+ .setPendingIntent(pendingIntent1)
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN)
+ .setIconAction(iconAction1)
+ .build()
+
+ @Test
+ fun getId_returnsId() {
+ assertThat(SafetyCenterEntry.Builder(entry1).setId("id_one").build().id)
+ .isEqualTo("id_one")
+ assertThat(SafetyCenterEntry.Builder(entry1).setId("id_two").build().id)
+ .isEqualTo("id_two")
+ }
+
+ @Test
+ fun getTitle_returnsTitle() {
+ assertThat(SafetyCenterEntry.Builder(entry1).setTitle("a title").build().title)
+ .isEqualTo("a title")
+ assertThat(SafetyCenterEntry.Builder(entry1).setTitle("another title").build().title)
+ .isEqualTo("another title")
+ }
+
+ @Test
+ fun getSummary_returnsSummary() {
+ assertThat(SafetyCenterEntry.Builder(entry1).setSummary("a summary").build().summary)
+ .isEqualTo("a summary")
+ assertThat(SafetyCenterEntry.Builder(entry1).setSummary("another summary").build().summary)
+ .isEqualTo("another summary")
+ assertThat(SafetyCenterEntry.Builder(entry1).setSummary(null).build().summary)
+ .isNull()
+ }
+
+ @Test
+ fun getSeverityLevel_returnsSeverityLevel() {
+ assertThat(SafetyCenterEntry.Builder(entry1)
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+ .build()
+ .severityLevel)
+ .isEqualTo(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+ assertThat(SafetyCenterEntry.Builder(entry1)
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+ .build()
+ .severityLevel)
+ .isEqualTo(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+ }
+
+ @Test
+ fun getStatelessIconType_returnsStatelessIconType() {
+ assertThat(entry1.statelessIconType).isEqualTo(SafetyCenterEntry.STATELESS_ICON_TYPE_NONE)
+ assertThat(SafetyCenterEntry.Builder(entry1)
+ .setStatelessIconType(SafetyCenterEntry.STATELESS_ICON_TYPE_PRIVACY)
+ .build()
+ .statelessIconType)
+ .isEqualTo(SafetyCenterEntry.STATELESS_ICON_TYPE_PRIVACY)
+ }
+
+ @Test
+ fun getPendingIntent_returnsPendingIntent() {
+ assertThat(SafetyCenterEntry.Builder(entry1)
+ .setPendingIntent(pendingIntent1)
+ .build()
+ .pendingIntent)
+ .isEqualTo(pendingIntent1)
+ assertThat(SafetyCenterEntry.Builder(entry1)
+ .setPendingIntent(pendingIntent2)
+ .build()
+ .pendingIntent)
+ .isEqualTo(pendingIntent2)
+ }
+
+ @Test
+ fun getIconAction_returnsIconAction() {
+ assertThat(SafetyCenterEntry.Builder(entry1).setIconAction(iconAction1).build().iconAction)
+ .isEqualTo(iconAction1)
+ assertThat(SafetyCenterEntry.Builder(entry1).setIconAction(iconAction2).build().iconAction)
+ .isEqualTo(iconAction2)
+ assertThat(SafetyCenterEntry.Builder(entry1).setIconAction(null).build().iconAction)
+ .isNull()
+ }
+
+ @Test
+ fun describeContents_returns0() {
+ assertThat(entry1.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ fun createFromParcel_withWriteToParcel_returnsEquivalentObject() {
+ val parcel = Parcel.obtain()
+
+ entry1.writeToParcel(parcel, /* flags= */ 0)
+ parcel.setDataPosition(0)
+
+ val fromParcel = SafetyCenterEntry.CREATOR.createFromParcel(parcel)
+ parcel.recycle()
+
+ assertThat(fromParcel).isEqualTo(entry1)
+ }
+
+ @Test
+ fun equals_hashCode_toString_equalByReference_areEqual() {
+ assertThat(entry1).isEqualTo(entry1)
+ assertThat(entry1.hashCode()).isEqualTo(entry1.hashCode())
+ assertThat(entry1.toString()).isEqualTo(entry1.toString())
+ }
+
+ @Test
+ fun equals_hashCode_toString_equalByValue_areEqual() {
+ val entry = SafetyCenterEntry.Builder("id")
+ .setTitle("a title")
+ .setSummary("a summary")
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+ .setStatelessIconType(SafetyCenterEntry.STATELESS_ICON_TYPE_PRIVACY)
+ .setPendingIntent(pendingIntent1)
+ .setIconAction(SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO, pendingIntent2)
+ .build()
+ val equivalentEntry = SafetyCenterEntry.Builder("id")
+ .setTitle("a title")
+ .setSummary("a summary")
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+ .setStatelessIconType(SafetyCenterEntry.STATELESS_ICON_TYPE_PRIVACY)
+ .setPendingIntent(pendingIntent1)
+ .setIconAction(SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO, pendingIntent2)
+ .build()
+
+ assertThat(entry).isEqualTo(equivalentEntry)
+ assertThat(entry.hashCode()).isEqualTo(equivalentEntry.hashCode())
+ assertThat(entry.toString()).isEqualTo(equivalentEntry.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentIds_areNotEqual() {
+ val differentFromEntry1 = SafetyCenterEntry.Builder(entry1)
+ .setId("a different id")
+ .build()
+
+ assertThat(differentFromEntry1).isNotEqualTo(entry1)
+ assertThat(differentFromEntry1.toString()).isNotEqualTo(entry1.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentTitles_areNotEqual() {
+ val differentFromEntry1 = SafetyCenterEntry.Builder(entry1)
+ .setTitle("a different title")
+ .build()
+
+ assertThat(differentFromEntry1).isNotEqualTo(entry1)
+ assertThat(differentFromEntry1.toString()).isNotEqualTo(entry1.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentSummaries_areNotEqual() {
+ val differentFromEntry1 = SafetyCenterEntry.Builder(entry1)
+ .setSummary("a different summary")
+ .build()
+
+ assertThat(differentFromEntry1).isNotEqualTo(entry1)
+ assertThat(differentFromEntry1.toString()).isNotEqualTo(entry1.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentSeverityLevels_areNotEqual() {
+ val differentFromEntry1 = SafetyCenterEntry.Builder(entry1)
+ .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING)
+ .build()
+
+ assertThat(differentFromEntry1).isNotEqualTo(entry1)
+ assertThat(differentFromEntry1.toString()).isNotEqualTo(entry1.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentStatelessIconTypes_areNotEqual() {
+ val differentFromEntry1 = SafetyCenterEntry.Builder(entry1)
+ .setStatelessIconType(SafetyCenterEntry.STATELESS_ICON_TYPE_PRIVACY)
+ .build()
+
+ assertThat(differentFromEntry1).isNotEqualTo(entry1)
+ assertThat(differentFromEntry1.toString()).isNotEqualTo(entry1.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentPendingIntents_areNotEqual() {
+ val differentFromEntry1 = SafetyCenterEntry.Builder(entry1)
+ .setPendingIntent(pendingIntent2)
+ .build()
+
+ assertThat(differentFromEntry1).isNotEqualTo(entry1)
+ assertThat(differentFromEntry1.toString()).isNotEqualTo(entry1.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentIconActions_areNotEqual() {
+ val differentFromEntry1 = SafetyCenterEntry.Builder(entry1)
+ .setIconAction(iconAction2)
+ .build()
+
+ assertThat(differentFromEntry1).isNotEqualTo(entry1)
+ assertThat(differentFromEntry1.toString()).isNotEqualTo(entry1.toString())
+ }
+
+ @Test
+ fun iconAction_getType_returnsType() {
+ assertThat(SafetyCenterEntry.IconAction(
+ SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent1)
+ .type)
+ .isEqualTo(SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR)
+ assertThat(SafetyCenterEntry.IconAction(
+ SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO, pendingIntent1)
+ .type)
+ .isEqualTo(SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO)
+ }
+
+ @Test
+ fun iconAction_getPendingIntent_returnsPendingIntent() {
+ assertThat(SafetyCenterEntry.IconAction(
+ SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent1)
+ .pendingIntent)
+ .isEqualTo(pendingIntent1)
+ assertThat(SafetyCenterEntry.IconAction(
+ SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent2)
+ .pendingIntent)
+ .isEqualTo(pendingIntent2)
+ }
+
+ @Test
+ fun iconAction_describeContents_returns0() {
+ assertThat(iconAction1.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ fun iconAction_createFromParcel_withWriteToParcel_returnsEquivalentObject() {
+ val parcel = Parcel.obtain()
+
+ iconAction1.writeToParcel(parcel, /* flags= */ 0)
+ parcel.setDataPosition(0)
+
+ val fromParcel = SafetyCenterEntry.IconAction.CREATOR.createFromParcel(parcel)
+ parcel.recycle()
+
+ assertThat(fromParcel).isEqualTo(iconAction1)
+ }
+
+ @Test
+ fun iconAction_equals_hashCode_toString_equalByReference_areEqual() {
+ assertThat(iconAction1).isEqualTo(iconAction1)
+ assertThat(iconAction1.hashCode()).isEqualTo(iconAction1.hashCode())
+ assertThat(iconAction1.toString()).isEqualTo(iconAction1.toString())
+ }
+
+ @Test
+ fun iconAction_equals_hashCode_toString_equalByValue_areEqual() {
+ val iconAction = SafetyCenterEntry.IconAction(
+ SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent1)
+ val equivalentIconAction = SafetyCenterEntry.IconAction(
+ SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent1)
+
+ assertThat(iconAction).isEqualTo(equivalentIconAction)
+ assertThat(iconAction.hashCode()).isEqualTo(equivalentIconAction.hashCode())
+ assertThat(iconAction.toString()).isEqualTo(equivalentIconAction.toString())
+ }
+
+ @Test
+ fun iconAction_equals_toString_differentTypes_areNotEqual() {
+ val iconAction = SafetyCenterEntry.IconAction(
+ SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent1)
+ val differentIconAction = SafetyCenterEntry.IconAction(
+ SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO, pendingIntent1)
+
+ assertThat(iconAction).isNotEqualTo(differentIconAction)
+ assertThat(iconAction.toString()).isNotEqualTo(differentIconAction.toString())
+ }
+
+ @Test
+ fun intentAction_equals_toString_differentPendingIntents_areNotEqual() {
+ val iconAction = SafetyCenterEntry.IconAction(
+ SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent1)
+ val differentIconAction = SafetyCenterEntry.IconAction(
+ SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent2)
+
+ assertThat(iconAction).isNotEqualTo(differentIconAction)
+ assertThat(iconAction.toString()).isNotEqualTo(differentIconAction.toString())
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt
new file mode 100644
index 0000000..b8820fb
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Parcel
+import android.safetycenter.SafetyCenterIssue
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SafetyCenterIssueTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val pendingIntent1 = PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ Intent("Fake Data"),
+ PendingIntent.FLAG_IMMUTABLE)
+ private val pendingIntent2 = PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ Intent("Fake Different Data"),
+ PendingIntent.FLAG_IMMUTABLE)
+
+ val action1 = SafetyCenterIssue.Action.Builder()
+ .setLabel("an action")
+ .setPendingIntent(pendingIntent1)
+ .setSuccessMessage("a success message")
+ .build()
+ val action2 = SafetyCenterIssue.Action.Builder()
+ .setLabel("another action")
+ .setPendingIntent(pendingIntent2)
+ .build()
+
+ val issue1 = SafetyCenterIssue.Builder("iSsUe_iD")
+ .setTitle("Everything's good")
+ .setSubtitle("In the neighborhood")
+ .setSummary("Please acknowledge this")
+ .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
+ .setActions(listOf(action1))
+ .build()
+
+ @Test
+ fun getId_returnsId() {
+ assertThat(SafetyCenterIssue.Builder(issue1).setId("an id").build().id)
+ .isEqualTo("an id")
+ assertThat(SafetyCenterIssue.Builder(issue1).setId("another id").build().id)
+ .isEqualTo("another id")
+ }
+
+ @Test
+ fun getTitle_returnsTitle() {
+ assertThat(SafetyCenterIssue.Builder(issue1).setTitle("a title").build().title)
+ .isEqualTo("a title")
+ assertThat(SafetyCenterIssue.Builder(issue1).setTitle("another title").build().title)
+ .isEqualTo("another title")
+ }
+
+ @Test
+ fun getSubtitle_returnsSubtitle() {
+ assertThat(SafetyCenterIssue.Builder(issue1).setSubtitle("a subtitle").build().subtitle)
+ .isEqualTo("a subtitle")
+ assertThat(
+ SafetyCenterIssue.Builder(issue1).setSubtitle("another subtitle").build().subtitle)
+ .isEqualTo("another subtitle")
+ }
+
+ @Test
+ fun getSummary_returnsSummary() {
+ assertThat(SafetyCenterIssue.Builder(issue1).setSummary("a summary").build().summary)
+ .isEqualTo("a summary")
+ assertThat(SafetyCenterIssue.Builder(issue1).setSummary("another summary").build().summary)
+ .isEqualTo("another summary")
+ }
+
+ @Test
+ fun getSeverityLevel_returnsSeverityLevel() {
+ assertThat(SafetyCenterIssue.Builder(issue1)
+ .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATION)
+ .build()
+ .severityLevel)
+ .isEqualTo(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATION)
+ assertThat(SafetyCenterIssue.Builder(issue1)
+ .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING)
+ .build()
+ .severityLevel)
+ .isEqualTo(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING)
+ }
+
+ @Test
+ fun isDismissible_returnsIsDismissible() {
+ assertThat(SafetyCenterIssue.Builder(issue1).setDismissible(true).build().isDismissible)
+ .isTrue()
+ assertThat(SafetyCenterIssue.Builder(issue1).setDismissible(false).build().isDismissible)
+ .isFalse()
+ }
+
+ @Test
+ fun shouldConfirmDismissal_returnsShouldConfirmDismissal() {
+ assertThat(SafetyCenterIssue.Builder(issue1)
+ .setShouldConfirmDismissal(true)
+ .build()
+ .shouldConfirmDismissal())
+ .isTrue()
+ assertThat(SafetyCenterIssue.Builder(issue1)
+ .setShouldConfirmDismissal(false)
+ .build()
+ .shouldConfirmDismissal())
+ .isFalse()
+ }
+
+ @Test
+ fun getActions_returnsActions() {
+ assertThat(SafetyCenterIssue.Builder(issue1)
+ .setActions(listOf(action1, action2))
+ .build().actions)
+ .containsExactly(action1, action2)
+ assertThat(SafetyCenterIssue.Builder(issue1).setActions(listOf(action2)).build().actions)
+ .containsExactly(action2)
+ assertThat(SafetyCenterIssue.Builder(issue1).setActions(listOf()).build().actions)
+ .isEmpty()
+ }
+
+ @Test
+ fun describeContents_returns0() {
+ assertThat(issue1.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ fun createFromParcel_withWriteToParcel_returnsEquivalentObject() {
+ val parcel = Parcel.obtain()
+
+ issue1.writeToParcel(parcel, /* flags= */ 0)
+ parcel.setDataPosition(0)
+
+ val fromParcel = SafetyCenterIssue.CREATOR.createFromParcel(parcel)
+ parcel.recycle()
+
+ assertThat(fromParcel).isEqualTo(issue1)
+ }
+
+ @Test
+ fun equals_hashCode_toString_equalByReference_areEqual() {
+ assertThat(issue1).isEqualTo(issue1)
+ assertThat(issue1.hashCode()).isEqualTo(issue1.hashCode())
+ assertThat(issue1.toString()).isEqualTo(issue1.toString())
+ }
+
+ @Test
+ fun equals_hashCode_toString_equalByValue_areEqual() {
+ val issue = SafetyCenterIssue.Builder("an id")
+ .setTitle("a title")
+ .setSubtitle("In the neighborhood")
+ .setSummary("Please acknowledge this")
+ .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
+ .setActions(listOf(action1))
+ .build()
+ }
+
+ @Test
+ fun equals_toString_differentIds_areNotEqual() {
+ val differentFromIssue1 = SafetyCenterIssue.Builder(issue1)
+ .setId("a different id")
+ .build()
+
+ assertThat(differentFromIssue1).isNotEqualTo(issue1)
+ assertThat(differentFromIssue1.toString()).isNotEqualTo(issue1.toString())
+ }
+
+ @Test
+ fun equals_toString_differentTitles_areNotEqual() {
+ val differentFromIssue1 = SafetyCenterIssue.Builder(issue1)
+ .setTitle("a different title")
+ .build()
+
+ assertThat(differentFromIssue1).isNotEqualTo(issue1)
+ assertThat(differentFromIssue1.toString()).isNotEqualTo(issue1.toString())
+ }
+
+ @Test
+ fun equals_toString_differentSubtitles_areNotEqual() {
+ val differentFromIssue1 = SafetyCenterIssue.Builder(issue1)
+ .setSubtitle("a different subtitle")
+ .build()
+
+ assertThat(differentFromIssue1).isNotEqualTo(issue1)
+ assertThat(differentFromIssue1.toString()).isNotEqualTo(issue1.toString())
+ }
+
+ @Test
+ fun equals_toString_differentSummaries_areNotEqual() {
+ val differentFromIssue1 = SafetyCenterIssue.Builder(issue1)
+ .setSummary("a different summary")
+ .build()
+
+ assertThat(differentFromIssue1).isNotEqualTo(issue1)
+ assertThat(differentFromIssue1.toString()).isNotEqualTo(issue1.toString())
+ }
+
+ @Test
+ fun equals_toString_differentSeverityLevels_areNotEqual() {
+ val differentFromIssue1 = SafetyCenterIssue.Builder(issue1)
+ .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING)
+ .build()
+
+ assertThat(differentFromIssue1).isNotEqualTo(issue1)
+ assertThat(differentFromIssue1.toString()).isNotEqualTo(issue1.toString())
+ }
+
+ @Test
+ fun equals_toString_differentIsDismissibleValues_areNotEqual() {
+ val differentFromIssue1 = SafetyCenterIssue.Builder(issue1)
+ .setDismissible(true)
+ .build()
+
+ assertThat(differentFromIssue1).isNotEqualTo(issue1)
+ assertThat(differentFromIssue1.toString()).isNotEqualTo(issue1.toString())
+ }
+
+ @Test
+ fun equals_toString_differentShouldConfirmDismissalValues_areNotEqual() {
+ val differentFromIssue1 = SafetyCenterIssue.Builder(issue1)
+ .setShouldConfirmDismissal(true)
+ .build()
+
+ assertThat(differentFromIssue1).isNotEqualTo(issue1)
+ assertThat(differentFromIssue1.toString()).isNotEqualTo(issue1.toString())
+ }
+
+ @Test
+ fun equals_toString_differentActions_areNotEqual() {
+ val differentFromIssue1 = SafetyCenterIssue.Builder(issue1)
+ .setActions(listOf(action2))
+ .build()
+
+ assertThat(differentFromIssue1).isNotEqualTo(issue1)
+ assertThat(differentFromIssue1.toString()).isNotEqualTo(issue1.toString())
+ }
+
+ @Test
+ fun action_getLabel_returnsLabel() {
+ assertThat(action1.label).isEqualTo("an action")
+ assertThat(action2.label).isEqualTo("another action")
+ }
+
+ @Test
+ fun action_getPendingIntent_returnsPendingIntent() {
+ assertThat(action1.pendingIntent).isEqualTo(pendingIntent1)
+ assertThat(action2.pendingIntent).isEqualTo(pendingIntent2)
+ }
+
+ @Test
+ fun action_getSuccessMessage_returnsSuccessMessage() {
+ assertThat(action1.successMessage).isEqualTo("a success message")
+ assertThat(action2.successMessage).isNull()
+ }
+
+ @Test
+ fun action_describeContents_returns0() {
+ assertThat(action1.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ fun action_createFromParcel_withWriteToParcel_returnsEquivalentObject() {
+ val parcel = Parcel.obtain()
+
+ action1.writeToParcel(parcel, /* flags= */ 0)
+
+ parcel.setDataPosition(0)
+ val fromParcel = SafetyCenterIssue.Action.CREATOR.createFromParcel(parcel)
+ parcel.recycle()
+
+ assertThat(fromParcel).isEqualTo(action1)
+ }
+
+ @Test
+ fun action_equals_hashCode_toString_equalByReference_areEqual() {
+ assertThat(action1).isEqualTo(action1)
+ assertThat(action1.hashCode()).isEqualTo(action1.hashCode())
+ assertThat(action1.toString()).isEqualTo(action1.toString())
+ }
+
+ @Test
+ fun action_equals_hashCode_toString_equalByValue_areEqual() {
+ val action = SafetyCenterIssue.Action.Builder()
+ .setLabel("a label")
+ .setPendingIntent(pendingIntent1)
+ .setSuccessMessage("a success message")
+ .build()
+ val equivalentAction = SafetyCenterIssue.Action.Builder()
+ .setLabel("a label")
+ .setPendingIntent(pendingIntent1)
+ .setSuccessMessage("a success message")
+ .build()
+
+ assertThat(action).isEqualTo(equivalentAction)
+ assertThat(action.toString()).isEqualTo(equivalentAction.toString())
+ }
+
+ @Test
+ fun action_equals_toString_differentLabels_areNotEqual() {
+ val action = SafetyCenterIssue.Action.Builder()
+ .setLabel("a label")
+ .setPendingIntent(pendingIntent1)
+ .setSuccessMessage("a success message")
+ .build()
+ val differentAction = SafetyCenterIssue.Action.Builder()
+ .setLabel("a different label")
+ .setPendingIntent(pendingIntent1)
+ .setSuccessMessage("a success message")
+ .build()
+
+ assertThat(action).isNotEqualTo(differentAction)
+ assertThat(action.toString()).isNotEqualTo(differentAction.toString())
+ }
+
+ @Test
+ fun action_equals_toString_differentPendingIntents_areNotEqual() {
+ val action = SafetyCenterIssue.Action.Builder()
+ .setLabel("a label")
+ .setPendingIntent(pendingIntent1)
+ .setSuccessMessage("a success message")
+ .build()
+ val differentAction = SafetyCenterIssue.Action.Builder()
+ .setLabel("a label")
+ .setPendingIntent(pendingIntent2)
+ .setSuccessMessage("a success message")
+ .build()
+
+ assertThat(action).isNotEqualTo(differentAction)
+ assertThat(action.toString()).isNotEqualTo(differentAction.toString())
+ }
+
+ @Test
+ fun action_equals_toString_differentSuccessMessages_areNotEqual() {
+ val action = SafetyCenterIssue.Action.Builder()
+ .setLabel("a label")
+ .setPendingIntent(pendingIntent1)
+ .setSuccessMessage("a success message")
+ .build()
+ val differentAction = SafetyCenterIssue.Action.Builder()
+ .setLabel("a label")
+ .setPendingIntent(pendingIntent1)
+ .setSuccessMessage("a different success message")
+ .build()
+
+ assertThat(action).isNotEqualTo(differentAction)
+ assertThat(action.toString()).isNotEqualTo(differentAction.toString())
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
new file mode 100644
index 0000000..a5c7dd8
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts
+
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.ACTION_SAFETY_CENTER
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.content.res.Resources
+import android.os.Build.VERSION_CODES.TIRAMISU
+import android.provider.DeviceConfig
+import android.safetycenter.SafetyCenterManager
+import android.safetycenter.SafetySourceData
+import android.safetycenter.SafetySourceIssue
+import android.safetycenter.SafetySourceIssue.SEVERITY_LEVEL_CRITICAL_WARNING
+import android.safetycenter.SafetySourceStatus
+import android.safetycenter.SafetySourceStatus.STATUS_LEVEL_CRITICAL_WARNING
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertFailsWith
+
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = TIRAMISU, codeName = "Tiramisu")
+class SafetyCenterManagerTest {
+ private val context: Context = getApplicationContext()
+ private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!!
+ private val somePendingIntent = PendingIntent.getActivity(
+ context, 0 /* requestCode */,
+ Intent(ACTION_SAFETY_CENTER).addFlags(FLAG_ACTIVITY_NEW_TASK),
+ FLAG_IMMUTABLE
+ )
+
+ @Before
+ @After
+ fun clearSafetyCenterDataBetweenTest() {
+ runWithShellPermissionIdentity {
+ safetyCenterManager.clearSafetyCenterData()
+ }
+ }
+
+ @Test
+ fun getLastSafetyCenterUpdate_noUpdate_returnsNull() {
+ val lastSafetyCenterUpdate = callWithShellPermissionIdentity {
+ safetyCenterManager.getLastSafetyCenterUpdate("some_unknown_id")
+ }
+
+ assertThat(lastSafetyCenterUpdate).isNull()
+ }
+
+ @Test
+ fun sendSafetyCenterUpdate_getLastSafetyCenterUpdateReturnsNewValue() {
+ val id = "some_known_id"
+ val safetyCenterUpdate = SafetySourceData.Builder(id).build()
+ runWithShellPermissionIdentity {
+ safetyCenterManager.sendSafetyCenterUpdate(safetyCenterUpdate)
+ }
+
+ val lastSafetyCenterUpdate = callWithShellPermissionIdentity {
+ safetyCenterManager.getLastSafetyCenterUpdate(id)
+ }
+
+ assertThat(lastSafetyCenterUpdate).isEqualTo(safetyCenterUpdate)
+ }
+
+ @Test
+ fun sendSafetyCenterUpdate_withSameId_replacesValue() {
+ val id = "some_known_id"
+ val firstSafetyCenterUpdate = SafetySourceData.Builder(id).build()
+ runWithShellPermissionIdentity {
+ safetyCenterManager.sendSafetyCenterUpdate(firstSafetyCenterUpdate)
+ }
+ val secondSafetyCenterUpdate = SafetySourceData.Builder(id).setStatus(
+ SafetySourceStatus.Builder(
+ "Status title", "Summary of the status", STATUS_LEVEL_CRITICAL_WARNING,
+ somePendingIntent
+ ).build()
+ ).addIssue(
+ SafetySourceIssue.Builder(
+ "Issue id", "Issue title", "Summary of the issue",
+ SEVERITY_LEVEL_CRITICAL_WARNING
+ ).addAction(
+ SafetySourceIssue.Action.Builder(
+ "Solve issue",
+ somePendingIntent
+ ).build()
+ ).build()
+ ).build()
+ runWithShellPermissionIdentity {
+ safetyCenterManager.sendSafetyCenterUpdate(secondSafetyCenterUpdate)
+ }
+
+ val lastSafetyCenterUpdate = callWithShellPermissionIdentity {
+ safetyCenterManager.getLastSafetyCenterUpdate(id)
+ }
+
+ assertThat(lastSafetyCenterUpdate).isEqualTo(secondSafetyCenterUpdate)
+ }
+
+ @Test
+ fun isSafetyCenterEnabled_whenConfigEnabled_andFlagEnabled_returnsTrue() {
+ if (!deviceSupportsSafetyCenter()) {
+ return
+ }
+
+ runWithShellPermissionIdentity {
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_SAFETY_CENTER_ENABLED,
+ /* value = */ true.toString(),
+ /* makeDefault = */ false
+ )
+ }
+
+ val isSafetyCenterEnabled = callWithShellPermissionIdentity {
+ safetyCenterManager.isSafetyCenterEnabled
+ }
+
+ assertThat(isSafetyCenterEnabled).isTrue()
+ }
+
+ @Test
+ fun isSafetyCenterEnabled_whenConfigEnabled_andFlagDisabled_returnsFalse() {
+ if (!deviceSupportsSafetyCenter()) {
+ return
+ }
+
+ runWithShellPermissionIdentity {
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_SAFETY_CENTER_ENABLED,
+ /* value = */ false.toString(),
+ /* makeDefault = */ false
+ )
+ }
+
+ val isSafetyCenterEnabled = callWithShellPermissionIdentity {
+ safetyCenterManager.isSafetyCenterEnabled
+ }
+
+ assertThat(isSafetyCenterEnabled).isFalse()
+ }
+
+ @Test
+ fun isSafetyCenterEnabled_whenConfigDisabled_andFlagEnabled_returnsFalse() {
+ if (deviceSupportsSafetyCenter()) {
+ return
+ }
+
+ runWithShellPermissionIdentity {
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_SAFETY_CENTER_ENABLED,
+ /* value = */ true.toString(),
+ /* makeDefault = */ false
+ )
+ }
+
+ val isSafetyCenterEnabled = callWithShellPermissionIdentity {
+ safetyCenterManager.isSafetyCenterEnabled
+ }
+
+ assertThat(isSafetyCenterEnabled).isFalse()
+ }
+
+ @Test
+ fun isSafetyCenterEnabled_whenConfigDisabled_andFlagDisabled_returnsFalse() {
+ if (deviceSupportsSafetyCenter()) {
+ return
+ }
+
+ runWithShellPermissionIdentity {
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_SAFETY_CENTER_ENABLED,
+ /* value = */ false.toString(),
+ /* makeDefault = */ false
+ )
+ }
+
+ val isSafetyCenterEnabled = callWithShellPermissionIdentity {
+ safetyCenterManager.isSafetyCenterEnabled
+ }
+
+ assertThat(isSafetyCenterEnabled).isFalse()
+ }
+
+ @Test
+ fun isSafetyCenterEnabled_whenAppDoesntHoldPermission_methodThrows() {
+ assertFailsWith(SecurityException::class) {
+ safetyCenterManager.isSafetyCenterEnabled
+ }
+ }
+
+ private fun deviceSupportsSafetyCenter() =
+ context.resources.getBoolean(
+ Resources.getSystem().getIdentifier(
+ "config_enableSafetyCenter",
+ "bool",
+ "android"
+ )
+ )
+
+ companion object {
+ /** Name of the flag that determines whether SafetyCenter is enabled. */
+ const val PROPERTY_SAFETY_CENTER_ENABLED = "safety_center_is_enabled"
+ }
+}
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterStatusTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterStatusTest.kt
new file mode 100644
index 0000000..c069085
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterStatusTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts
+
+import android.os.Parcel
+import android.safetycenter.SafetyCenterStatus
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SafetyCenterStatusTest {
+
+ val baseStatus = SafetyCenterStatus.Builder()
+ .setTitle("This is my title")
+ .setSummary("This is my summary")
+ .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION)
+ .build()
+
+ @Test
+ fun getTitle_returnsTitle() {
+ assertThat(SafetyCenterStatus.Builder(baseStatus).setTitle("title").build().title)
+ .isEqualTo("title")
+
+ assertThat(SafetyCenterStatus.Builder(baseStatus).setTitle("different title").build().title)
+ .isEqualTo("different title")
+ }
+
+ @Test
+ fun getSummary_returnsSummary() {
+ assertThat(SafetyCenterStatus.Builder(baseStatus).setSummary("summary").build().summary)
+ .isEqualTo("summary")
+
+ assertThat(
+ SafetyCenterStatus.Builder(baseStatus)
+ .setSummary("different summary")
+ .build()
+ .summary)
+ .isEqualTo("different summary")
+ }
+
+ @Test
+ fun getSeverityLevel_returnsSeverityLevel() {
+ assertThat(
+ SafetyCenterStatus.Builder(baseStatus)
+ .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK)
+ .build()
+ .severityLevel)
+ .isEqualTo(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK)
+
+ assertThat(
+ SafetyCenterStatus.Builder(baseStatus)
+ .setSeverityLevel(
+ SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
+ .build()
+ .severityLevel)
+ .isEqualTo(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
+ }
+
+ @Test
+ fun describeContents_returns0() {
+ assertThat(baseStatus.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ fun createFromParcel_withWriteToParcel_returnsEquivalentObject() {
+ val parcel: Parcel = Parcel.obtain()
+ baseStatus.writeToParcel(parcel, 0 /* flags */)
+ parcel.setDataPosition(0)
+ val fromParcel = SafetyCenterStatus.CREATOR.createFromParcel(parcel)
+ parcel.recycle()
+
+ assertThat(fromParcel).isEqualTo(baseStatus)
+ }
+
+ @Test
+ fun equals_hashCode_toString_equalByReference_areEqual() {
+ assertThat(baseStatus).isEqualTo(baseStatus)
+ assertThat(baseStatus.hashCode()).isEqualTo(baseStatus.hashCode())
+ assertThat(baseStatus.toString()).isEqualTo(baseStatus.toString())
+ }
+
+ @Test
+ fun equals_hashCode_toString_equalByValue_areEqual() {
+ val status = SafetyCenterStatus.Builder()
+ .setTitle("same title")
+ .setSummary("same summary")
+ .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK)
+ .build()
+ val equivalentStatus = SafetyCenterStatus.Builder()
+ .setTitle("same title")
+ .setSummary("same summary")
+ .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK)
+ .build()
+
+ assertThat(status).isEqualTo(equivalentStatus)
+ assertThat(status.hashCode()).isEqualTo(equivalentStatus.hashCode())
+ assertThat(status.toString()).isEqualTo(equivalentStatus.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentTitles_areNotEqual() {
+ val unequalStatus = SafetyCenterStatus.Builder(baseStatus)
+ .setTitle("that's discarsting")
+ .build()
+
+ assertThat(unequalStatus).isNotEqualTo(baseStatus)
+ assertThat(unequalStatus.toString()).isNotEqualTo(baseStatus.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentSummaries_areNotEqual() {
+ val unequalStatus = SafetyCenterStatus.Builder(baseStatus)
+ .setSummary("discarsting sheet")
+ .build()
+
+ assertThat(unequalStatus).isNotEqualTo(baseStatus)
+ assertThat(unequalStatus.toString()).isNotEqualTo(baseStatus.toString())
+ }
+
+ @Test
+ fun equals_toString_withDifferentSeverityLevels_arNotEqual() {
+ val unequalStatus = SafetyCenterStatus.Builder(baseStatus)
+ .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK)
+ .build()
+
+ assertThat(unequalStatus).isNotEqualTo(baseStatus)
+ assertThat(unequalStatus.toString()).isNotEqualTo(baseStatus.toString())
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt
new file mode 100644
index 0000000..0f69501
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts
+
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
+import android.content.Context
+import android.content.Intent
+import android.os.Build.VERSION_CODES.TIRAMISU
+import android.os.Parcel
+import android.safetycenter.SafetySourceData
+import android.safetycenter.SafetySourceIssue
+import android.safetycenter.SafetySourceIssue.ISSUE_CATEGORY_ACCOUNT
+import android.safetycenter.SafetySourceStatus
+import android.safetycenter.SafetySourceStatus.IconAction.ICON_TYPE_GEAR
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** CTS tests for [SafetySourceData]. */
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = TIRAMISU, codeName = "Tiramisu")
+class SafetySourceDataTest {
+ private val context: Context = getApplicationContext()
+
+ private val status1 = SafetySourceStatus.Builder(
+ "Status title 1",
+ "Status summary 1",
+ SafetySourceStatus.STATUS_LEVEL_NONE,
+ PendingIntent.getActivity(context, 0 /* requestCode= */,
+ Intent("Status PendingIntent 1"), FLAG_IMMUTABLE))
+ .build()
+ private val status2 = SafetySourceStatus.Builder(
+ "Status title 2",
+ "Status summary 2",
+ SafetySourceStatus.STATUS_LEVEL_RECOMMENDATION,
+ PendingIntent.getActivity(context, 0 /* requestCode= */,
+ Intent("Status PendingIntent 2"), FLAG_IMMUTABLE))
+ .setIconAction(SafetySourceStatus.IconAction(ICON_TYPE_GEAR,
+ PendingIntent.getActivity(context, 0 /* requestCode= */,
+ Intent("IconAction PendingIntent 2"), FLAG_IMMUTABLE)))
+ .build()
+ private val issue1 = SafetySourceIssue.Builder(
+ "Issue id 1",
+ "Issue summary 1",
+ "Issue summary 1",
+ SafetySourceIssue.SEVERITY_LEVEL_INFORMATION)
+ .setSubtitle("Issue subtitle 1")
+ .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+ .addAction(SafetySourceIssue.Action.Builder("Action label 1",
+ PendingIntent.getActivity(context, 0 /* requestCode= */,
+ Intent("Issue PendingIntent 1"), FLAG_IMMUTABLE))
+ .build())
+ .build()
+ private val issue2 = SafetySourceIssue.Builder(
+ "Issue id 2",
+ "Issue title 2",
+ "Issue summary 2",
+ SafetySourceIssue.SEVERITY_LEVEL_RECOMMENDATION)
+ .addAction(SafetySourceIssue.Action.Builder("Action label 2",
+ PendingIntent.getService(context, 0 /* requestCode= */,
+ Intent("Issue PendingIntent 2"), FLAG_IMMUTABLE)).build())
+ .setOnDismissPendingIntent(PendingIntent.getService(context,
+ 0 /* requestCode= */,
+ Intent("Issue OnDismissPendingIntent 2"), FLAG_IMMUTABLE))
+ .build()
+
+ @Test
+ fun getId_returnsId() {
+ val safetySourceData = SafetySourceData.Builder("Safety source id").build()
+
+ assertThat(safetySourceData.id).isEqualTo("Safety source id")
+ }
+
+ @Test
+ fun getStatus_withDefaultBuilder_returnsNull() {
+ val safetySourceData = SafetySourceData.Builder("Safety source id").build()
+
+ assertThat(safetySourceData.status).isNull()
+ }
+
+ @Test
+ fun getStatus_whenSetExplicitly_returnsStatus() {
+ val safetySourceData = SafetySourceData.Builder("Safety source id")
+ .setStatus(status1)
+ .build()
+
+ assertThat(safetySourceData.status).isEqualTo(status1)
+ }
+
+ @Test
+ fun getIssues_withDefaultBuilder_returnsEmptyList() {
+ val safetySourceData = SafetySourceData.Builder("Safety source id").build()
+
+ assertThat(safetySourceData.issues).isEmpty()
+ }
+
+ @Test
+ fun getIssues_whenSetExplicitly_returnsIssues() {
+ val safetySourceData = SafetySourceData.Builder("Safety source id")
+ .addIssue(issue1)
+ .addIssue(issue2)
+ .build()
+
+ assertThat(safetySourceData.issues).containsExactly(issue1, issue2).inOrder()
+ }
+
+ @Test
+ fun describeContents_returns0() {
+ val safetySourceData = SafetySourceData.Builder("Safety source id")
+ .setStatus(status1)
+ .addIssue(issue1)
+ .addIssue(issue2)
+ .build()
+
+ assertThat(safetySourceData.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ fun createFromParcel_withWriteToParcel_returnsOriginalSafetySourceData() {
+ val safetySourceData = SafetySourceData.Builder("Safety source id")
+ .setStatus(status1)
+ .addIssue(issue1)
+ .addIssue(issue2)
+ .build()
+
+ val parcel: Parcel = Parcel.obtain()
+ safetySourceData.writeToParcel(parcel, 0 /* flags */)
+ parcel.setDataPosition(0)
+ val safetySourceDataFromParcel: SafetySourceData =
+ SafetySourceData.CREATOR.createFromParcel(parcel)
+ parcel.recycle()
+
+ assertThat(safetySourceDataFromParcel).isEqualTo(safetySourceData)
+ }
+
+ // TODO(b/208473675): Use `EqualsTester` for testing `hashcode` and `equals`.
+ @Test
+ fun hashCode_equals_toString_withEqualByReference_withoutStatusAndIssues_areEqual() {
+ val safetySourceData = SafetySourceData.Builder("Safety source id").build()
+ val otherSafetySourceData = safetySourceData
+
+ assertThat(safetySourceData.hashCode()).isEqualTo(otherSafetySourceData.hashCode())
+ assertThat(safetySourceData).isEqualTo(otherSafetySourceData)
+ assertThat(safetySourceData.toString()).isEqualTo(otherSafetySourceData.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withEqualByReference_withoutIssues_areEqual() {
+ val safetySourceData = SafetySourceData.Builder("Safety source id")
+ .setStatus(status1)
+ .build()
+ val otherSafetySourceData = safetySourceData
+
+ assertThat(safetySourceData.hashCode()).isEqualTo(otherSafetySourceData.hashCode())
+ assertThat(safetySourceData).isEqualTo(otherSafetySourceData)
+ assertThat(safetySourceData.toString()).isEqualTo(otherSafetySourceData.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withEqualByReference_areEqual() {
+ val safetySourceData = SafetySourceData.Builder("Safety source id")
+ .setStatus(status1)
+ .addIssue(issue1)
+ .addIssue(issue2)
+ .build()
+ val otherSafetySourceData = safetySourceData
+
+ assertThat(safetySourceData.hashCode()).isEqualTo(otherSafetySourceData.hashCode())
+ assertThat(safetySourceData).isEqualTo(otherSafetySourceData)
+ assertThat(safetySourceData.toString()).isEqualTo(otherSafetySourceData.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withAllFieldsEqual_areEqual() {
+ val safetySourceData = SafetySourceData.Builder("Safety source id")
+ .setStatus(status1)
+ .addIssue(issue1)
+ .addIssue(issue2)
+ .build()
+ val otherSafetySourceData = SafetySourceData.Builder("Safety source id")
+ .setStatus(status1)
+ .addIssue(issue1)
+ .addIssue(issue2)
+ .build()
+
+ assertThat(safetySourceData.hashCode()).isEqualTo(otherSafetySourceData.hashCode())
+ assertThat(safetySourceData).isEqualTo(otherSafetySourceData)
+ assertThat(safetySourceData.toString()).isEqualTo(otherSafetySourceData.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withDifferentIds_areNotEqual() {
+ val safetySourceData = SafetySourceData.Builder("Safety source id")
+ .setStatus(status1)
+ .build()
+ val otherSafetySourceData = SafetySourceData.Builder("Safety source id 2")
+ .setStatus(status1)
+ .build()
+
+ assertThat(safetySourceData.hashCode()).isNotEqualTo(otherSafetySourceData.hashCode())
+ assertThat(safetySourceData).isNotEqualTo(otherSafetySourceData)
+ assertThat(safetySourceData.toString()).isNotEqualTo(otherSafetySourceData.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withDifferentIssues_areNotEqual() {
+ val safetySourceData = SafetySourceData.Builder("Safety source id")
+ .setStatus(status1)
+ .addIssue(issue1)
+ .addIssue(issue2)
+ .build()
+ val otherSafetySourceData = SafetySourceData.Builder("Safety source id")
+ .setStatus(status2)
+ .addIssue(issue1)
+ .addIssue(issue2)
+ .build()
+
+ assertThat(safetySourceData.hashCode()).isNotEqualTo(otherSafetySourceData.hashCode())
+ assertThat(safetySourceData).isNotEqualTo(otherSafetySourceData)
+ assertThat(safetySourceData.toString()).isNotEqualTo(otherSafetySourceData.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withDifferentStatuses_areNotEqual() {
+ val safetySourceData = SafetySourceData.Builder("Safety source id")
+ .setStatus(status1)
+ .addIssue(issue1)
+ .addIssue(issue2)
+ .build()
+ val otherSafetySourceData = SafetySourceData.Builder("Safety source id")
+ .setStatus(status1)
+ .addIssue(issue1)
+ .build()
+
+ assertThat(safetySourceData.hashCode()).isNotEqualTo(otherSafetySourceData.hashCode())
+ assertThat(safetySourceData).isNotEqualTo(otherSafetySourceData)
+ assertThat(safetySourceData.toString()).isNotEqualTo(otherSafetySourceData.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withStatusSetInOneAndNotOther_areNotEqual() {
+ val safetySourceData = SafetySourceData.Builder("Safety source id")
+ .setStatus(status1)
+ .build()
+ val otherSafetySourceData = SafetySourceData.Builder("Safety source id").build()
+
+ assertThat(safetySourceData.hashCode()).isNotEqualTo(otherSafetySourceData.hashCode())
+ assertThat(safetySourceData).isNotEqualTo(otherSafetySourceData)
+ assertThat(safetySourceData.toString()).isNotEqualTo(otherSafetySourceData.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withIssuesSetInOneAndNotOther_areNotEqual() {
+ val safetySourceData = SafetySourceData.Builder("Safety source id")
+ .setStatus(status1)
+ .addIssue(issue1)
+ .addIssue(issue2)
+ .build()
+ val otherSafetySourceData = SafetySourceData.Builder("Safety source id")
+ .setStatus(status1)
+ .build()
+
+ assertThat(safetySourceData.hashCode()).isNotEqualTo(otherSafetySourceData.hashCode())
+ assertThat(safetySourceData).isNotEqualTo(otherSafetySourceData)
+ assertThat(safetySourceData.toString()).isNotEqualTo(otherSafetySourceData.toString())
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt
new file mode 100644
index 0000000..545da83
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt
@@ -0,0 +1,614 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts
+
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
+import android.content.Context
+import android.content.Intent
+import android.os.Build.VERSION_CODES.TIRAMISU
+import android.os.Parcel
+import android.safetycenter.SafetySourceIssue
+import android.safetycenter.SafetySourceIssue.ISSUE_CATEGORY_ACCOUNT
+import android.safetycenter.SafetySourceIssue.ISSUE_CATEGORY_DEVICE
+import android.safetycenter.SafetySourceIssue.ISSUE_CATEGORY_GENERAL
+import android.safetycenter.SafetySourceIssue.Action
+import android.safetycenter.SafetySourceIssue.SEVERITY_LEVEL_CRITICAL_WARNING
+import android.safetycenter.SafetySourceIssue.SEVERITY_LEVEL_INFORMATION
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** CTS tests for [SafetySourceIssue]. */
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = TIRAMISU, codeName = "Tiramisu")
+class SafetySourceIssueTest {
+ private val context: Context = getApplicationContext()
+
+ private val pendingIntent1: PendingIntent = PendingIntent.getActivity(context,
+ 0 /* requestCode= */, Intent("PendingIntent 1"), FLAG_IMMUTABLE)
+ private val action1 = Action.Builder("Action label 1", pendingIntent1).build()
+ private val pendingIntent2: PendingIntent = PendingIntent.getActivity(context,
+ 0 /* requestCode= */, Intent("PendingIntent 2"), FLAG_IMMUTABLE)
+ private val action2 = Action.Builder("Action label 2", pendingIntent2).build()
+
+ @Test
+ fun action_getLabel_returnsLabel() {
+ val action = Action.Builder("Action label", pendingIntent1).build()
+
+ assertThat(action.label).isEqualTo("Action label")
+ }
+
+ @Test
+ fun action_isResolving_withDefaultBuilder_returnsFalse() {
+ val action = Action.Builder("Action label", pendingIntent1).build()
+
+ assertThat(action.isResolving).isFalse()
+ }
+
+ @Test
+ fun action_isResolving_whenSetExplicitly_returnsResolving() {
+ val action = Action.Builder("Action label", pendingIntent1)
+ .setResolving(true)
+ .build()
+
+ assertThat(action.isResolving).isTrue()
+ }
+
+ @Test
+ fun action_getPendingIntent_returnsPendingIntent() {
+ val action = Action.Builder("Action label", pendingIntent1).build()
+
+ assertThat(action.pendingIntent).isEqualTo(pendingIntent1)
+ }
+
+ @Test
+ fun action_describeContents_returns0() {
+ val action = Action.Builder("Action label", pendingIntent1).build()
+
+ assertThat(action.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ fun action_createFromParcel_withWriteToParcel_returnsOriginalAction() {
+ val action = Action.Builder("Action label", pendingIntent1).build()
+
+ val parcel: Parcel = Parcel.obtain()
+ action.writeToParcel(parcel, 0 /* flags */)
+ parcel.setDataPosition(0)
+ val actionFromParcel: Action = Action.CREATOR.createFromParcel(parcel)
+ parcel.recycle()
+
+ assertThat(actionFromParcel).isEqualTo(action)
+ }
+
+ // TODO(b/208473675): Use `EqualsTester` for testing `hashcode` and `equals`.
+ @Test
+ fun action_hashCode_equals_toString_withEqualByReferenceActions_areEqual() {
+ val action = Action.Builder("Action label", pendingIntent1).build()
+ val otherAction = action
+
+ assertThat(action.hashCode()).isEqualTo(otherAction.hashCode())
+ assertThat(action).isEqualTo(otherAction)
+ assertThat(action.toString()).isEqualTo(otherAction.toString())
+ }
+
+ @Test
+ fun action_hashCode_equals_toString_withAllFieldsEqual_areEqual() {
+ val action = Action.Builder("Action label", pendingIntent1).build()
+ val otherAction = Action.Builder("Action label", pendingIntent1).build()
+
+ assertThat(action.hashCode()).isEqualTo(otherAction.hashCode())
+ assertThat(action).isEqualTo(otherAction)
+ assertThat(action.toString()).isEqualTo(otherAction.toString())
+ }
+
+ @Test
+ fun action_hashCode_equals_toString_withDifferentLabels_areNotEqual() {
+ val action = Action.Builder("Action label", pendingIntent1).build()
+ val otherAction = Action.Builder("Other action label", pendingIntent1).build()
+
+ assertThat(action.hashCode()).isNotEqualTo(otherAction.hashCode())
+ assertThat(action).isNotEqualTo(otherAction)
+ assertThat(action.toString()).isNotEqualTo(otherAction.toString())
+ }
+
+ @Test
+ fun action_hashCode_equals_toString_withDifferentResolving_areNotEqual() {
+ val action =
+ Action.Builder("Action label", pendingIntent1).setResolving(false)
+ .build()
+ val otherAction =
+ Action.Builder("Action label", pendingIntent1).setResolving(true).build()
+
+ assertThat(action.hashCode()).isNotEqualTo(otherAction.hashCode())
+ assertThat(action).isNotEqualTo(otherAction)
+ assertThat(action.toString()).isNotEqualTo(otherAction.toString())
+ }
+
+ @Test
+ fun action_hashCode_equals_toString_withDifferentPendingIntents_areNotEqual() {
+ val action = Action.Builder("Action label",
+ PendingIntent.getActivity(context, 0 /* requestCode= */,
+ Intent("Action PendingIntent"), FLAG_IMMUTABLE))
+ .build()
+ val otherAction = Action.Builder("Action label",
+ PendingIntent.getActivity(context, 0 /* requestCode= */,
+ Intent("Other action PendingIntent"), FLAG_IMMUTABLE))
+ .build()
+
+ assertThat(action.hashCode()).isNotEqualTo(otherAction.hashCode())
+ assertThat(action).isNotEqualTo(otherAction)
+ assertThat(action.toString()).isNotEqualTo(otherAction.toString())
+ }
+
+ @Test
+ fun getId_returnsId() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.id).isEqualTo("Issue id")
+ }
+
+ @Test
+ fun getTitle_returnsTitle() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.title).isEqualTo("Issue title")
+ }
+
+ @Test
+ fun getSubtitle_withDefaultBuilder_returnsNull() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.subtitle).isNull()
+ }
+
+ @Test
+ fun getSubtitle_whenSetExplicitly_returnsSubtitle() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .setSubtitle("Issue subtitle")
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.subtitle).isEqualTo("Issue subtitle")
+ }
+
+ @Test
+ fun getSummary_returnsSummary() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.summary).isEqualTo("Issue summary")
+ }
+
+ @Test
+ fun getSeverityLevel_returnsSeverityLevel() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.severityLevel).isEqualTo(SEVERITY_LEVEL_INFORMATION)
+ }
+
+ @Test
+ fun getIssueCategory_withDefaultBuilder_returnsGeneralCategory() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.issueCategory).isEqualTo(ISSUE_CATEGORY_GENERAL)
+ }
+
+ @Test
+ fun getIssueCategory_whenSetExplicitly_returnsIssueCategory() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .setIssueCategory(ISSUE_CATEGORY_DEVICE)
+ .build()
+
+ assertThat(safetySourceIssue.issueCategory).isEqualTo(ISSUE_CATEGORY_DEVICE)
+ }
+
+ @Test
+ fun getActions_returnsActions() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .addAction(action2)
+ .build()
+
+ assertThat(safetySourceIssue.actions).containsExactly(action1, action2).inOrder()
+ }
+
+ @Test
+ fun getOnDismissPendingIntent_withDefaultBuilder_returnsNull() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.onDismissPendingIntent).isNull()
+ }
+
+ @Test
+ fun getOnDismissPendingIntent_whenSetExplicitly_returnsOnDismissPendingIntent() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .setOnDismissPendingIntent(pendingIntent1)
+ .build()
+
+ assertThat(safetySourceIssue.onDismissPendingIntent).isEqualTo(pendingIntent1)
+ }
+
+ @Test
+ fun build_withNoActions_throwsIllegalArgumentException() {
+ val safetySourceIssueBuilder = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+
+ assertThrows("Safety source issue must contain at least 1 action",
+ IllegalArgumentException::class.java) { safetySourceIssueBuilder.build() }
+ }
+
+ @Test
+ fun build_withMoreThanTwoActions_throwsIllegalArgumentException() {
+ val safetySourceIssueBuilder = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .addAction(action2)
+ .addAction(action1)
+
+ assertThrows("Safety source issue must not contain more than 2 actions",
+ IllegalArgumentException::class.java) { safetySourceIssueBuilder.build() }
+ }
+
+ @Test
+ fun describeContents_returns0() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .setSubtitle("Issue subtitle")
+ .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+ .addAction(action1)
+ .addAction(action2)
+ .setOnDismissPendingIntent(pendingIntent1)
+ .build()
+
+ assertThat(safetySourceIssue.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ fun createFromParcel_withWriteToParcel_returnsOriginalSafetySourceIssue() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .setSubtitle("Issue subtitle")
+ .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+ .addAction(action1)
+ .addAction(action2)
+ .setOnDismissPendingIntent(pendingIntent1)
+ .build()
+
+ val parcel: Parcel = Parcel.obtain()
+ safetySourceIssue.writeToParcel(parcel, 0 /* flags */)
+ parcel.setDataPosition(0)
+ val safetySourceIssueFromParcel: SafetySourceIssue =
+ SafetySourceIssue.CREATOR.createFromParcel(parcel)
+ parcel.recycle()
+
+ assertThat(safetySourceIssueFromParcel).isEqualTo(safetySourceIssue)
+ }
+
+ // TODO(b/208473675): Use `EqualsTester` for testing `hashcode` and `equals`.
+ @Test
+ fun hashCode_equals_toString_withEqualByReferenceSafetySourceIssues_areEqual() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .setSubtitle("Issue subtitle")
+ .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+ .addAction(action1)
+ .addAction(action2)
+ .setOnDismissPendingIntent(pendingIntent1)
+ .build()
+ val otherSafetySourceIssue = safetySourceIssue
+
+ assertThat(safetySourceIssue.hashCode()).isEqualTo(otherSafetySourceIssue.hashCode())
+ assertThat(safetySourceIssue).isEqualTo(otherSafetySourceIssue)
+ assertThat(safetySourceIssue.toString()).isEqualTo(otherSafetySourceIssue.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withAllFieldsEqual_areEqual() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .setSubtitle("Issue subtitle")
+ .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+ .addAction(Action.Builder("Action label 1", pendingIntent1)
+ .setResolving(false)
+ .build())
+ .setOnDismissPendingIntent(pendingIntent1)
+ .build()
+ val otherSafetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .setSubtitle("Issue subtitle")
+ .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+ .addAction(Action.Builder("Action label 1", pendingIntent1)
+ .setResolving(false)
+ .build())
+ .setOnDismissPendingIntent(pendingIntent1)
+ .build()
+
+ assertThat(safetySourceIssue.hashCode()).isEqualTo(otherSafetySourceIssue.hashCode())
+ assertThat(safetySourceIssue).isEqualTo(otherSafetySourceIssue)
+ assertThat(safetySourceIssue.toString()).isEqualTo(otherSafetySourceIssue.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withDifferentIds_areNotEqual() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .build()
+ val otherSafetySourceIssue = SafetySourceIssue.Builder(
+ "Other issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.hashCode()).isNotEqualTo(otherSafetySourceIssue.hashCode())
+ assertThat(safetySourceIssue).isNotEqualTo(otherSafetySourceIssue)
+ assertThat(safetySourceIssue.toString()).isNotEqualTo(otherSafetySourceIssue.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withDifferentTitles_areNotEqual() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .build()
+ val otherSafetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Other issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.hashCode()).isNotEqualTo(otherSafetySourceIssue.hashCode())
+ assertThat(safetySourceIssue).isNotEqualTo(otherSafetySourceIssue)
+ assertThat(safetySourceIssue.toString()).isNotEqualTo(otherSafetySourceIssue.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withDifferentSubtitles_areNotEqual() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .setSubtitle("Issue subtitle")
+ .addAction(action1)
+ .build()
+ val otherSafetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .setSubtitle("Other issue subtitle")
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.hashCode()).isNotEqualTo(otherSafetySourceIssue.hashCode())
+ assertThat(safetySourceIssue).isNotEqualTo(otherSafetySourceIssue)
+ assertThat(safetySourceIssue.toString()).isNotEqualTo(otherSafetySourceIssue.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withDifferentSummaries_areNotEqual() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .build()
+ val otherSafetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Other issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.hashCode()).isNotEqualTo(otherSafetySourceIssue.hashCode())
+ assertThat(safetySourceIssue).isNotEqualTo(otherSafetySourceIssue)
+ assertThat(safetySourceIssue.toString()).isNotEqualTo(otherSafetySourceIssue.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withDifferentSeverityLevels_areNotEqual() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .build()
+ val otherSafetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_CRITICAL_WARNING)
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.hashCode()).isNotEqualTo(otherSafetySourceIssue.hashCode())
+ assertThat(safetySourceIssue).isNotEqualTo(otherSafetySourceIssue)
+ assertThat(safetySourceIssue.toString()).isNotEqualTo(otherSafetySourceIssue.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withDifferentIssueCategories_areNotEqual() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .setIssueCategory(ISSUE_CATEGORY_DEVICE)
+ .build()
+ val otherSafetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .setIssueCategory(ISSUE_CATEGORY_GENERAL)
+ .build()
+
+ assertThat(safetySourceIssue.hashCode()).isNotEqualTo(otherSafetySourceIssue.hashCode())
+ assertThat(safetySourceIssue).isNotEqualTo(otherSafetySourceIssue)
+ assertThat(safetySourceIssue.toString()).isNotEqualTo(otherSafetySourceIssue.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withDifferentActions_areNotEqual() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .addAction(action2)
+ .build()
+ val otherSafetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action2)
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.hashCode()).isNotEqualTo(otherSafetySourceIssue.hashCode())
+ assertThat(safetySourceIssue).isNotEqualTo(otherSafetySourceIssue)
+ assertThat(safetySourceIssue.toString()).isNotEqualTo(otherSafetySourceIssue.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withDifferentOnDismissPendingIntents_areNotEqual() {
+ val safetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .setOnDismissPendingIntent(pendingIntent1)
+ .build()
+ val otherSafetySourceIssue = SafetySourceIssue.Builder(
+ "Issue id",
+ "Other issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION)
+ .addAction(action1)
+ .setOnDismissPendingIntent(pendingIntent2)
+ .build()
+
+ assertThat(safetySourceIssue.hashCode()).isNotEqualTo(otherSafetySourceIssue.hashCode())
+ assertThat(safetySourceIssue).isNotEqualTo(otherSafetySourceIssue)
+ assertThat(safetySourceIssue.toString()).isNotEqualTo(otherSafetySourceIssue.toString())
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt
new file mode 100644
index 0000000..7d9b511
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts
+
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
+import android.content.Context
+import android.content.Intent
+import android.os.Build.VERSION_CODES.TIRAMISU
+import android.os.Parcel
+import android.safetycenter.SafetySourceStatus
+import android.safetycenter.SafetySourceStatus.IconAction
+import android.safetycenter.SafetySourceStatus.IconAction.ICON_TYPE_GEAR
+import android.safetycenter.SafetySourceStatus.IconAction.ICON_TYPE_INFO
+import android.safetycenter.SafetySourceStatus.STATUS_LEVEL_CRITICAL_WARNING
+import android.safetycenter.SafetySourceStatus.STATUS_LEVEL_NO_ISSUES
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** CTS tests for [SafetySourceStatus]. */
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = TIRAMISU, codeName = "Tiramisu")
+class SafetySourceStatusTest {
+ private val context: Context = getApplicationContext()
+
+ private val pendingIntent1: PendingIntent = PendingIntent.getActivity(context,
+ 0 /* requestCode= */, Intent("PendingIntent 1"), FLAG_IMMUTABLE)
+ private val iconAction1 = IconAction(ICON_TYPE_INFO, pendingIntent1)
+ private val pendingIntent2: PendingIntent = PendingIntent.getActivity(context,
+ 0 /* requestCode= */, Intent("PendingIntent 2"), FLAG_IMMUTABLE)
+ private val iconAction2 = IconAction(ICON_TYPE_GEAR, pendingIntent2)
+
+ @Test
+ fun iconAction_getIconType_returnsIconType() {
+ val iconAction = IconAction(ICON_TYPE_INFO, pendingIntent1)
+
+ assertThat(iconAction.iconType).isEqualTo(ICON_TYPE_INFO)
+ }
+
+ @Test
+ fun iconAction_getPendingIntent_returnsPendingIntent() {
+ val iconAction = IconAction(ICON_TYPE_GEAR, pendingIntent1)
+
+ assertThat(iconAction.pendingIntent).isEqualTo(pendingIntent1)
+ }
+
+ @Test
+ fun iconAction_describeContents_returns0() {
+ val iconAction = IconAction(ICON_TYPE_GEAR, pendingIntent1)
+
+ assertThat(iconAction.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ fun iconAction_createFromParcel_withWriteToParcel_returnsOriginalAction() {
+ val iconAction = IconAction(ICON_TYPE_GEAR, pendingIntent1)
+
+ val parcel: Parcel = Parcel.obtain()
+ iconAction.writeToParcel(parcel, 0 /* flags */)
+ parcel.setDataPosition(0)
+ val iconActionFromParcel: IconAction = IconAction.CREATOR.createFromParcel(parcel)
+ parcel.recycle()
+
+ assertThat(iconActionFromParcel).isEqualTo(iconAction)
+ }
+
+ // TODO(b/208473675): Use `EqualsTester` for testing `hashcode` and `equals`.
+ @Test
+ fun iconAction_hashCode_equals_toString_withEqualByReferenceIconActions_areEqual() {
+ val iconAction = IconAction(ICON_TYPE_GEAR, pendingIntent1)
+ val otherIconAction = iconAction
+
+ assertThat(iconAction.hashCode()).isEqualTo(otherIconAction.hashCode())
+ assertThat(iconAction).isEqualTo(otherIconAction)
+ assertThat(iconAction.toString()).isEqualTo(otherIconAction.toString())
+ }
+
+ @Test
+ fun iconAction_hashCode_equals_toString_withAllFieldsEqual_areEqual() {
+ val iconAction = IconAction(ICON_TYPE_GEAR, pendingIntent1)
+ val otherIconAction = IconAction(ICON_TYPE_GEAR, pendingIntent1)
+
+ assertThat(iconAction.hashCode()).isEqualTo(otherIconAction.hashCode())
+ assertThat(iconAction).isEqualTo(otherIconAction)
+ assertThat(iconAction.toString()).isEqualTo(otherIconAction.toString())
+ }
+
+ @Test
+ fun iconAction_hashCode_equals_toString_withDifferentIconTypes_areNotEqual() {
+ val iconAction = IconAction(ICON_TYPE_GEAR, pendingIntent1)
+ val otherIconAction = IconAction(ICON_TYPE_INFO, pendingIntent1)
+
+ assertThat(iconAction.hashCode()).isNotEqualTo(otherIconAction.hashCode())
+ assertThat(iconAction).isNotEqualTo(otherIconAction)
+ assertThat(iconAction.toString()).isNotEqualTo(otherIconAction.toString())
+ }
+
+ @Test
+ fun iconAction_hashCode_equals_toString_withDifferentPendingIntents_areNotEqual() {
+ val iconAction = IconAction(ICON_TYPE_GEAR, pendingIntent1)
+ val otherIconAction = IconAction(ICON_TYPE_GEAR, pendingIntent2)
+
+ assertThat(iconAction.hashCode()).isNotEqualTo(otherIconAction.hashCode())
+ assertThat(iconAction).isNotEqualTo(otherIconAction)
+ assertThat(iconAction.toString()).isNotEqualTo(otherIconAction.toString())
+ }
+
+ @Test
+ fun getTitle_returnsTitle() {
+ val safetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ pendingIntent1)
+ .build()
+
+ assertThat(safetySourceStatus.title).isEqualTo("Status title")
+ }
+
+ @Test
+ fun getSummary_returnsSummary() {
+ val safetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ pendingIntent1)
+ .build()
+
+ assertThat(safetySourceStatus.summary).isEqualTo("Status summary")
+ }
+
+ @Test
+ fun getStatusLevel_returnsStatusLevel() {
+ val safetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ pendingIntent1)
+ .build()
+
+ assertThat(safetySourceStatus.statusLevel).isEqualTo(STATUS_LEVEL_NO_ISSUES)
+ }
+
+ @Test
+ fun getPendingIntent_returnsPendingIntent() {
+ val safetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ pendingIntent1)
+ .build()
+
+ assertThat(safetySourceStatus.pendingIntent).isEqualTo(pendingIntent1)
+ }
+
+ @Test
+ fun getIconAction_withDefaultBuilder_returnsNull() {
+ val safetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ pendingIntent1)
+ .build()
+
+ assertThat(safetySourceStatus.iconAction).isNull()
+ }
+
+ @Test
+ fun getIconAction_whenSetExplicitly_returnsIconAction() {
+ val safetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ pendingIntent1)
+ .setIconAction(iconAction1)
+ .build()
+
+ assertThat(safetySourceStatus.iconAction).isEqualTo(iconAction1)
+ }
+
+ @Test
+ fun describeContents_returns0() {
+ val safetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ pendingIntent1)
+ .setIconAction(iconAction1)
+ .build()
+
+ assertThat(safetySourceStatus.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ fun createFromParcel_withWriteToParcel_returnsOriginalSafetySourceStatus() {
+ val safetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ pendingIntent1)
+ .setIconAction(iconAction1)
+ .build()
+
+ val parcel: Parcel = Parcel.obtain()
+ safetySourceStatus.writeToParcel(parcel, 0 /* flags */)
+ parcel.setDataPosition(0)
+ val safetySourceStatusFromParcel: SafetySourceStatus =
+ SafetySourceStatus.CREATOR.createFromParcel(parcel)
+ parcel.recycle()
+
+ assertThat(safetySourceStatusFromParcel).isEqualTo(safetySourceStatus)
+ }
+
+ // TODO(b/208473675): Use `EqualsTester` for testing `hashcode` and `equals`.
+ @Test
+ fun hashCode_equals_toString_withEqualByReferenceSafetySourceStatuses_areEqual() {
+ val safetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ pendingIntent1)
+ .setIconAction(iconAction1)
+ .build()
+ val otherSafetySourceStatus = safetySourceStatus
+
+ assertThat(safetySourceStatus.hashCode()).isEqualTo(otherSafetySourceStatus.hashCode())
+ assertThat(safetySourceStatus).isEqualTo(otherSafetySourceStatus)
+ assertThat(safetySourceStatus.toString()).isEqualTo(otherSafetySourceStatus.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withAllFieldsEqual_areEqual() {
+ val safetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ pendingIntent1)
+ .setIconAction(iconAction1)
+ .build()
+ val otherSafetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ pendingIntent1)
+ .setIconAction(iconAction1)
+ .build()
+
+ assertThat(safetySourceStatus.hashCode()).isEqualTo(otherSafetySourceStatus.hashCode())
+ assertThat(safetySourceStatus).isEqualTo(otherSafetySourceStatus)
+ assertThat(safetySourceStatus.toString()).isEqualTo(otherSafetySourceStatus.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withDifferentTitles_areNotEqual() {
+ val safetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ pendingIntent1)
+ .build()
+ val otherSafetySourceStatus = SafetySourceStatus.Builder(
+ "Other status title",
+ "Status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ pendingIntent1)
+ .build()
+
+ assertThat(safetySourceStatus.hashCode()).isNotEqualTo(otherSafetySourceStatus.hashCode())
+ assertThat(safetySourceStatus).isNotEqualTo(otherSafetySourceStatus)
+ assertThat(safetySourceStatus.toString()).isNotEqualTo(otherSafetySourceStatus.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withDifferentSummaries_areNotEqual() {
+ val safetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ pendingIntent1)
+ .build()
+ val otherSafetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Other status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ pendingIntent1)
+ .build()
+
+ assertThat(safetySourceStatus.hashCode()).isNotEqualTo(otherSafetySourceStatus.hashCode())
+ assertThat(safetySourceStatus).isNotEqualTo(otherSafetySourceStatus)
+ assertThat(safetySourceStatus.toString()).isNotEqualTo(otherSafetySourceStatus.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withDifferentStatusLevels_areNotEqual() {
+ val safetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ pendingIntent1)
+ .build()
+ val otherSafetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_CRITICAL_WARNING,
+ pendingIntent1)
+ .build()
+
+ assertThat(safetySourceStatus.hashCode()).isNotEqualTo(otherSafetySourceStatus.hashCode())
+ assertThat(safetySourceStatus).isNotEqualTo(otherSafetySourceStatus)
+ assertThat(safetySourceStatus.toString()).isNotEqualTo(otherSafetySourceStatus.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withDifferentPendingIntents_areNotEqual() {
+ val safetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_NO_ISSUES,
+ PendingIntent.getActivity(context, 0 /* requestCode= */,
+ Intent("Status PendingIntent"), FLAG_IMMUTABLE))
+ .build()
+ val otherSafetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_CRITICAL_WARNING,
+ PendingIntent.getActivity(context, 0 /* requestCode= */,
+ Intent("Other status PendingIntent"), FLAG_IMMUTABLE))
+ .build()
+
+ assertThat(safetySourceStatus.hashCode()).isNotEqualTo(otherSafetySourceStatus.hashCode())
+ assertThat(safetySourceStatus).isNotEqualTo(otherSafetySourceStatus)
+ assertThat(safetySourceStatus.toString()).isNotEqualTo(otherSafetySourceStatus.toString())
+ }
+
+ @Test
+ fun hashCode_equals_toString_withDifferentIconActions_areNotEqual() {
+ val safetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_CRITICAL_WARNING,
+ pendingIntent1)
+ .setIconAction(iconAction1)
+ .build()
+ val otherSafetySourceStatus = SafetySourceStatus.Builder(
+ "Status title",
+ "Status summary",
+ STATUS_LEVEL_CRITICAL_WARNING,
+ pendingIntent1)
+ .setIconAction(iconAction2)
+ .build()
+
+ assertThat(safetySourceStatus.hashCode()).isNotEqualTo(otherSafetySourceStatus.hashCode())
+ assertThat(safetySourceStatus).isNotEqualTo(otherSafetySourceStatus)
+ assertThat(safetySourceStatus.toString()).isNotEqualTo(otherSafetySourceStatus.toString())
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/security/src/android/security/cts/FlagSlipperyTest.kt b/tests/tests/security/src/android/security/cts/FlagSlipperyTest.kt
index 8fe8054..eea7f56 100644
--- a/tests/tests/security/src/android/security/cts/FlagSlipperyTest.kt
+++ b/tests/tests/security/src/android/security/cts/FlagSlipperyTest.kt
@@ -30,6 +30,7 @@
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+import android.view.WindowManager.LayoutParams.FLAG_SLIPPERY
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -50,8 +51,6 @@
import java.util.concurrent.LinkedBlockingQueue
import java.util.Queue
-val FLAG_SLIPPERY = 0x20000000 // android.view.WindowManager.LayoutParams.FLAG_SLIPPERY
-
private fun getViewCenterOnScreen(v: View): Pair<Float, Float> {
val location = IntArray(2)
v.getLocationOnScreen(location)
diff --git a/tests/tests/security/src/android/security/cts/MotionEventTest.java b/tests/tests/security/src/android/security/cts/MotionEventTest.java
index c291ee4..dcdfc9b 100644
--- a/tests/tests/security/src/android/security/cts/MotionEventTest.java
+++ b/tests/tests/security/src/android/security/cts/MotionEventTest.java
@@ -39,8 +39,8 @@
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
-import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.Before;
import org.junit.Rule;
@@ -70,7 +70,7 @@
public void setup() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mActivity = mActivityRule.getActivity();
- PollingCheck.waitFor(mActivity::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity);
}
/**
diff --git a/tests/tests/security/src/android/security/cts/WallpaperManagerTest.java b/tests/tests/security/src/android/security/cts/WallpaperManagerTest.java
new file mode 100644
index 0000000..4b10c34
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/WallpaperManagerTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.net.Uri;
+import android.platform.test.annotations.AsbSecurityTest;
+import android.platform.test.annotations.RequiresDevice;
+import android.util.Log;
+import android.view.Display;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class WallpaperManagerTest {
+ private static final String TAG = "WallpaperManagerSTS";
+
+ private Context mContext;
+ private WallpaperManager mWallpaperManager;
+
+ @Before
+ public void setUp() {
+ InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.SET_WALLPAPER_HINTS,
+ Manifest.permission.SET_WALLPAPER);
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ mWallpaperManager = WallpaperManager.getInstance(mContext);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ // b/204316511
+ @Test
+ @AsbSecurityTest(cveBugId = 204316511)
+ public void testSetDisplayPadding() {
+ Rect validRect = new Rect(1, 1, 1, 1);
+ // This should work, no exception expected
+ mWallpaperManager.setDisplayPadding(validRect);
+
+ Rect negativeRect = new Rect(-1, 0 , 0, 0);
+ try {
+ mWallpaperManager.setDisplayPadding(negativeRect);
+ Assert.fail("setDisplayPadding should fail for a Rect with negative values");
+ } catch (IllegalArgumentException e) {
+ //Expected exception
+ }
+
+ DisplayManager dm = mContext.getSystemService(DisplayManager.class);
+ Display primaryDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+ Context windowContext = mContext.createWindowContext(primaryDisplay,
+ TYPE_APPLICATION, null);
+ Display display = windowContext.getDisplay();
+
+ Rect tooWideRect = new Rect(0, 0, display.getMaximumSizeDimension() + 1, 0);
+ try {
+ mWallpaperManager.setDisplayPadding(tooWideRect);
+ Assert.fail("setDisplayPadding should fail for a Rect width larger than "
+ + display.getMaximumSizeDimension());
+ } catch (IllegalArgumentException e) {
+ //Expected exception
+ }
+
+ Rect tooHighRect = new Rect(0, 0, 0, display.getMaximumSizeDimension() + 1);
+ try {
+ mWallpaperManager.setDisplayPadding(tooHighRect);
+ Assert.fail("setDisplayPadding should fail for a Rect height larger than "
+ + display.getMaximumSizeDimension());
+ } catch (IllegalArgumentException e) {
+ //Expected exception
+ }
+ }
+
+ @RequiresDevice
+ @Test(expected = IllegalArgumentException.class)
+ @AsbSecurityTest(cveBugId = 204087139)
+ public void testSetMaliciousStream() {
+ ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
+ mContext.getSystemService(ActivityManager.class).getMemoryInfo(memoryInfo);
+ final long exploitSize = (long) (memoryInfo.totalMem * 0.95);
+ final File maliciousImageFile = generateMaliciousImageFile(exploitSize, memoryInfo);
+ if (maliciousImageFile == null) {
+ throw new IllegalStateException(
+ "failed generating malicious image file, size=" + exploitSize);
+ }
+ try (InputStream s = mContext.getContentResolver()
+ .openInputStream(Uri.fromFile(maliciousImageFile))) {
+ mWallpaperManager.setStream(
+ s, null, true, WallpaperManager.FLAG_LOCK | WallpaperManager.FLAG_SYSTEM);
+ throw new IllegalStateException(
+ "setStream with size " + exploitSize + " shouldn't succeed!");
+ } catch (IOException ex) {
+ } finally {
+ if (maliciousImageFile.exists()) {
+ maliciousImageFile.delete();
+ }
+ }
+ }
+
+ private File generateMaliciousImageFile(long exploitSize, ActivityManager.MemoryInfo memInfo) {
+ File maliciousFile = new File(mContext.getExternalFilesDir(null) + "/exploit.png");
+ try (BufferedOutputStream bos = new BufferedOutputStream(
+ new FileOutputStream(maliciousFile), (int) (exploitSize * 0.01))) {
+ if (!maliciousFile.exists()) {
+ maliciousFile.createNewFile();
+ }
+ Log.v(TAG, "start generating: ram=" + memInfo.totalMem + ", exploit=" + exploitSize);
+ for (long i = 0; i < exploitSize; i++) {
+ bos.write(0xFF);
+ }
+ Log.v(TAG, "Generate successfully!");
+ return maliciousFile;
+ } catch (Exception ex) {
+ Log.w(TAG, "failed at generating malicious image file, ram="
+ + memInfo.totalMem + ", exploit=" + exploitSize, ex);
+ return null;
+ }
+ }
+}
diff --git a/tests/tests/selinux/OWNERS b/tests/tests/selinux/OWNERS
index 8824b03..20c32c2 100644
--- a/tests/tests/selinux/OWNERS
+++ b/tests/tests/selinux/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 85141
jeffv@google.com
jgalenson@google.com
-nnk@google.com
diff --git a/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt b/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
index bd21f9f..f0b84e5 100644
--- a/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
+++ b/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
@@ -40,6 +40,7 @@
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assume
@@ -60,6 +61,8 @@
"android.sensorprivacy.cts.usemiccamera.action.USE_MIC_CAM"
const val MIC_CAM_OVERLAY_ACTIVITY_ACTION =
"android.sensorprivacy.cts.usemiccamera.overlay.action.USE_MIC_CAM"
+ const val SHOW_OVERLAY_ACTION =
+ "android.sensorprivacy.cts.usemiccamera.action.SHOW_OVERLAY_ACTION"
const val FINISH_MIC_CAM_ACTIVITY_ACTION =
"android.sensorprivacy.cts.usemiccamera.action.FINISH_USE_MIC_CAM"
const val USE_MIC_EXTRA =
@@ -213,17 +216,20 @@
}
fun unblockSensorWithDialogAndAssert() {
- val buttonResId = if (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
- "com.android.systemui:id/bottom_sheet_positive_button"
- } else {
- "android:id/button1"
- }
+ val buttonResId = getDialogPositiveButtonId()
UiAutomatorUtils.waitFindObject(By.res(buttonResId)).click()
eventually {
assertFalse(isSensorPrivacyEnabled())
}
}
+ private fun getDialogPositiveButtonId() =
+ if (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+ "com.android.systemui:id/bottom_sheet_positive_button"
+ } else {
+ "android:id/button1"
+ }
+
@Test
@AppModeFull(reason = "Uses secondary app, instant apps have no visibility")
fun testOpNotRunningWhileSensorPrivacyEnabled() {
@@ -335,6 +341,8 @@
fun testTapjacking() {
setSensor(true)
startTestOverlayApp(false)
+ assertNotNull("Dialog never showed",
+ UiAutomatorUtils.waitFindObject(By.res(getDialogPositiveButtonId())))
val view = UiAutomatorUtils.waitFindObjectOrNull(By.text("This Should Be Hidden"), 10_000)
assertNull("Overlay should not have shown.", view)
}
@@ -367,6 +375,8 @@
context.startActivity(intent)
// Wait for app to open
UiAutomatorUtils.waitFindObject(By.textContains(ACTIVITY_TITLE_SNIP))
+
+ context.sendBroadcast(Intent(SHOW_OVERLAY_ACTION))
}
private fun finishTestApp() {
diff --git a/tests/tests/sensorprivacy/test-apps/CtsUseMicOrCameraAndOverlayForSensorPrivacy/src/android/sensorprivacy/cts/usemiccamera/overlay/UseMicCamera.kt b/tests/tests/sensorprivacy/test-apps/CtsUseMicOrCameraAndOverlayForSensorPrivacy/src/android/sensorprivacy/cts/usemiccamera/overlay/UseMicCamera.kt
index 127564e..4eda205 100644
--- a/tests/tests/sensorprivacy/test-apps/CtsUseMicOrCameraAndOverlayForSensorPrivacy/src/android/sensorprivacy/cts/usemiccamera/overlay/UseMicCamera.kt
+++ b/tests/tests/sensorprivacy/test-apps/CtsUseMicOrCameraAndOverlayForSensorPrivacy/src/android/sensorprivacy/cts/usemiccamera/overlay/UseMicCamera.kt
@@ -38,6 +38,8 @@
companion object {
const val MIC_CAM_OVERLAY_ACTIVITY_ACTION =
"android.sensorprivacy.cts.usemiccamera.overlay.action.USE_MIC_CAM"
+ const val SHOW_OVERLAY_ACTION =
+ "android.sensorprivacy.cts.usemiccamera.action.SHOW_OVERLAY_ACTION"
const val FINISH_MIC_CAM_ACTIVITY_ACTION =
"android.sensorprivacy.cts.usemiccamera.action.FINISH_USE_MIC_CAM"
const val USE_MIC_EXTRA =
@@ -70,6 +72,16 @@
}
}, IntentFilter(FINISH_MIC_CAM_ACTIVITY_ACTION))
+ registerReceiver(object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ val intent = Intent(this@UseMicCamera, OverlayActivity::class.java)
+ if (intent.getBooleanExtra(DELAYED_ACTIVITY_NEW_TASK_EXTRA, false)) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ startActivity(intent)
+ }
+ }, IntentFilter(SHOW_OVERLAY_ACTION))
+
val useMic = intent.getBooleanExtra(USE_MIC_EXTRA, false)
val useCam = intent.getBooleanExtra(USE_CAM_EXTRA, false)
if (useMic) {
@@ -80,13 +92,5 @@
cam = openCam(this, intent.getBooleanExtra(UseMicCamera.RETRY_CAM_EXTRA, false))
}, 1000)
}
-
- handler.postDelayed({
- val intent = Intent(this, OverlayActivity::class.java)
- if (intent.getBooleanExtra(DELAYED_ACTIVITY_NEW_TASK_EXTRA, false)) {
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- }
- startActivity(intent)
- }, 2000)
}
}
diff --git a/tests/tests/settings/src/android/settings/cts/AppLocaleSettingsTest.java b/tests/tests/settings/src/android/settings/cts/AppLocaleSettingsTest.java
new file mode 100644
index 0000000..85f5e5d
--- /dev/null
+++ b/tests/tests/settings/src/android/settings/cts/AppLocaleSettingsTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.settings.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests to ensure the Activity to handle
+ * {@link Settings#ACTION_APP_LOCALE_SETTINGS}
+ */
+@RunWith(AndroidJUnit4.class)
+public class AppLocaleSettingsTest {
+ @Test
+ public void testAppLocaleSettingsExist() throws RemoteException {
+ final Intent intent = new Intent(Settings.ACTION_APP_LOCALE_SETTINGS);
+ intent.setData(Uri.parse("package:com.my.app"));
+ final ResolveInfo ri = InstrumentationRegistry.getTargetContext()
+ .getPackageManager().resolveActivity(
+ intent, PackageManager.MATCH_DEFAULT_ONLY);
+ assertTrue(ri != null);
+ }
+}
diff --git a/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java b/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java
index b6103c8..bd7e263 100644
--- a/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java
+++ b/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java
@@ -52,14 +52,7 @@
@Before
public void setUp() throws Exception {
- // runOnMainSync or SplitController#isSplitSupported will return wrong value for large
- // screen devices.
- InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mIsSplitSupported = SplitController.getInstance().isSplitSupported();
- }
- });
+ mIsSplitSupported = SplitController.getInstance().isSplitSupported();
mDeepLinkIntentResolveInfo = InstrumentationRegistry.getInstrumentation().getContext()
.getPackageManager().resolveActivity(
new Intent(Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY),
diff --git a/tests/tests/shortcutmanager/Android.bp b/tests/tests/shortcutmanager/Android.bp
index 776ce70..fab3c5e 100644
--- a/tests/tests/shortcutmanager/Android.bp
+++ b/tests/tests/shortcutmanager/Android.bp
@@ -26,6 +26,9 @@
"android.test.runner",
"android.test.base",
],
+ static_libs: [
+ "permission-test-util-lib",
+ ],
srcs: ["src/**/*.java"],
test_suites: [
"cts",
diff --git a/tests/tests/shortcutmanager/common/src/android.content.pm.cts.shortcutmanager.common/ReplyUtil.java b/tests/tests/shortcutmanager/common/src/android.content.pm.cts.shortcutmanager.common/ReplyUtil.java
index b1b0ae5..9186338 100644
--- a/tests/tests/shortcutmanager/common/src/android.content.pm.cts.shortcutmanager.common/ReplyUtil.java
+++ b/tests/tests/shortcutmanager/common/src/android.content.pm.cts.shortcutmanager.common/ReplyUtil.java
@@ -83,7 +83,8 @@
}
};
- context.registerReceiver(resultReceiver, filter);
+ context.registerReceiver(resultReceiver, filter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
try {
// Run the code.
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 92335e9..7a9c997 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
@@ -25,21 +25,14 @@
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
import android.app.PendingIntent;
-import android.app.appsearch.AppSearchManager;
-import android.app.appsearch.SearchResult;
-import android.app.appsearch.SearchSpec;
import android.content.ComponentName;
import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
-import android.content.pm.Signature;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.ArraySet;
import com.android.compatibility.common.util.CddTest;
@@ -49,13 +42,6 @@
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
/**
* Tests for {@link ShortcutManager} and {@link ShortcutInfo}.
@@ -607,6 +593,54 @@
testSetDynamicShortcuts_details();
}
+ public void testSetDynamicShortcuts_withCapabilities() {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcutBuilder("s1")
+ .setShortLabel("title1")
+ .setIntent(new Intent("main").putExtra("k1", "yyy"))
+ .addCapabilityBinding("action.intent.START_EXERCISE",
+ "exercise.type", list("running", "jogging"))
+ .addCapabilityBinding("action.intent.START_EXERCISE",
+ "exercise.duration", list("10m"))
+ .build(),
+ makeShortcutBuilder("s2")
+ .setShortLabel("title2")
+ .setIntent(new Intent("main").putExtra("k1", "yyy"))
+ .addCapabilityBinding("action.intent.STOP_EXERCISE",
+ "exercise.type", list("running", "jogging"))
+ .build(),
+ makeShortcut("s3", "title3"))));
+ });
+
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1", "s2", "s3")
+ .forShortcutWithId("s1", si -> {
+ assertTrue(si.hasCapability("action.intent.START_EXERCISE"));
+ assertFalse(si.hasCapability("action.intent.STOP_EXERCISE"));
+ assertEquals(list("running", "jogging"),
+ si.getCapabilityParameterValues("action.intent.START_EXERCISE",
+ "exercise.type"));
+ assertEquals(list("10m"),
+ si.getCapabilityParameterValues("action.intent.START_EXERCISE",
+ "exercise.duration"));
+ })
+ .forShortcutWithId("s2", si -> {
+ assertFalse(si.hasCapability("action.intent.START_EXERCISE"));
+ assertTrue(si.hasCapability("action.intent.STOP_EXERCISE"));
+ assertEquals(list("running", "jogging"),
+ si.getCapabilityParameterValues("action.intent.STOP_EXERCISE",
+ "exercise.type"));
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("title3", si.getShortLabel());
+ });
+ });
+ }
+
public void testAddDynamicShortcuts() {
runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().addDynamicShortcuts(list(
@@ -2226,6 +2260,127 @@
});
}
+ public void testSetShortcutsExcludedFromLauncher_ExcludedFromSearchResults() {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1", "title1"),
+ makeShortcut("s2", "title2"),
+ makeShortcut("s3", "title3"),
+ makeShortcutExcludedFromLauncher("s4"))));
+ });
+
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1", "s2", "s3")
+ .forShortcutWithId("s1", si -> {
+ assertEquals("title1", si.getShortLabel());
+ })
+ .forShortcutWithId("s2", si -> {
+ assertEquals("title2", si.getShortLabel());
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("title3", si.getShortLabel());
+ });
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ // Publish from different package.
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1x", "title1x"),
+ makeShortcutExcludedFromLauncher("seven"))));
+ });
+
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1x")
+ .forShortcutWithId("s1x", si -> {
+ assertEquals("title1x", si.getShortLabel());
+ });
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ // Package 1 still has the same shortcuts.
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1", "s2", "s3")
+ .forShortcutWithId("s1", si -> {
+ assertEquals("title1", si.getShortLabel());
+ })
+ .forShortcutWithId("s2", si -> {
+ assertEquals("title2", si.getShortLabel());
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("title3", si.getShortLabel());
+ });
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s2", "title2-updated"),
+ makeShortcutExcludedFromLauncher("s4"))));
+ });
+
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s2")
+ .forShortcutWithId("s2", si -> {
+ assertEquals("title2-updated", si.getShortLabel());
+ });
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(
+ list(makeShortcutExcludedFromLauncher("s4"))));
+ });
+
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .isEmpty();
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ // Package2 still has the same shortcuts.
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1x")
+ .forShortcutWithId("s1x", si -> {
+ assertEquals("title1x", si.getShortLabel());
+ });
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+ }
+
// TODO Test auto rank adjustment.
// TODO Test save & load.
}
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 7abd0a2..0e60f4b 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
@@ -37,10 +37,14 @@
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
import android.os.UserHandle;
-import androidx.annotation.NonNull;
+import android.provider.DeviceConfig;
import android.test.InstrumentationTestCase;
import android.text.TextUtils;
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.SystemUtil;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -450,6 +454,17 @@
}
/**
+ * Make a shortcut excluded from launcher with an ID.
+ */
+ protected ShortcutInfo makeShortcutExcludedFromLauncher(String id) {
+ return makeShortcut(
+ id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
+ makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0,
+ /* locusId =*/ null, /* longLived =*/ true,
+ /* excludedSurfaces */ ShortcutInfo.SURFACE_LAUNCHER);
+ }
+
+ /**
* Make multiple shortcuts with IDs.
*/
protected List<ShortcutInfo> makeShortcuts(String... ids) {
@@ -482,11 +497,21 @@
*/
protected ShortcutInfo makeShortcut(String id, String shortLabel, ComponentName activity,
Icon icon, Intent intent, int rank, LocusId locusId, boolean longLived) {
+ return makeShortcut(id, shortLabel, activity, icon, intent, rank, locusId, longLived, 0);
+ }
+
+ /**
+ * Make a shortcut with details.
+ */
+ protected ShortcutInfo makeShortcut(String id, String shortLabel, ComponentName activity,
+ Icon icon, Intent intent, int rank, LocusId locusId, boolean longLived,
+ int excludedSurfaces) {
final ShortcutInfo.Builder b = makeShortcutBuilder(id)
.setShortLabel(shortLabel)
.setRank(rank)
.setIntent(intent)
- .setLongLived(longLived);
+ .setLongLived(longLived)
+ .setExcludedFromSurfaces(excludedSurfaces);
if (activity != null) {
b.setActivity(activity);
} else if (mTargetActivityOverride != null) {
@@ -619,4 +644,10 @@
}
return getLauncherApps().getShortcuts(q, getUserHandle());
}
+
+ protected boolean isAppSearchEnabled() {
+ return SystemUtil.runWithShellPermissionIdentity(() ->
+ DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ "shortcut_appsearch_integration", false));
+ }
}
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 33b9c90..1b9c48b 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
@@ -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_GET_PERSISTED_DATA;
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;
@@ -26,11 +27,16 @@
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
import android.content.LocusId;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Icon;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.util.List;
@SmallTest
public class ShortcutManagerLauncherApiTest extends ShortcutManagerCtsTestsBase {
@@ -411,4 +417,271 @@
assertIconDimensions(icon2, getIconAsLauncher(
mLauncherContext1, mPackageContext1.getPackageName(), "s2", false));
}
+
+ public void testSetDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
+ if (!isAppSearchEnabled()) {
+ return;
+ }
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ final ShortcutManager manager = getManager();
+ // Verifies setDynamicShortcuts persists shortcuts into AppSearch
+ manager.setDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s2"),
+ makeShortcutExcludedFromLauncher("s3")
+ ));
+ // Verify shortcut excluded from launcher are not included in search result
+ assertWith(manager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC))
+ .haveIds("s1", "s2")
+ .areAllDynamic();
+ });
+ Thread.sleep(5000);
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
+ final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+ FLAG_GET_PERSISTED_DATA,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list("s1", "s2", "s3"),
+ null);
+ // Package 1
+ assertWith(ret).haveIds("s1", "s2", "s3");
+ });
+ }
+
+ public void testRemoveAllDynamicShortcuts_RemovesShortcutsFromDisk() throws Exception {
+ if (!isAppSearchEnabled()) {
+ return;
+ }
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ final ShortcutManager manager = getManager();
+ // Verifies setDynamicShortcuts persists shortcuts into AppSearch
+ manager.setDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s2"),
+ makeShortcutExcludedFromLauncher("s3")
+ ));
+ // Verify shortcut excluded from launcher are not included in search result
+ assertWith(manager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC))
+ .haveIds("s1", "s2")
+ .areAllDynamic();
+ });
+ Thread.sleep(5000);
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ // Verifies removeAllDynamicShortcuts removes shortcuts from persistence layer
+ getManager().removeAllDynamicShortcuts();
+ });
+ Thread.sleep(5000);
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
+ final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+ FLAG_GET_PERSISTED_DATA,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list("s1", "s2", "s3"),
+ null);
+ assertWith(ret).isEmpty();
+ });
+ }
+
+ public void testAddDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
+ if (!isAppSearchEnabled()) {
+ return;
+ }
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ final ShortcutManager manager = getManager();
+ manager.setDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s2"),
+ makeShortcutExcludedFromLauncher("s3")
+ ));
+ // Verify shortcut excluded from launcher are not included in search result
+ assertWith(manager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC))
+ .haveIds("s1", "s2")
+ .areAllDynamic();
+ // Verifies addDynamicShortcuts persists shortcuts into AppSearch
+ manager.addDynamicShortcuts(list(makeShortcut("s4"), makeShortcut("s5")));
+ });
+ Thread.sleep(5000);
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
+ final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+ FLAG_GET_PERSISTED_DATA,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list("s1", "s2", "s3", "s4", "s5"),
+ null);
+ assertWith(ret).haveIds("s1", "s2", "s3", "s4", "s5");
+ });
+ }
+
+ public void testPushDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
+ if (!isAppSearchEnabled()) {
+ return;
+ }
+ SystemUtil.runShellCommand("cmd shortcut override-config max_shortcuts=5");
+ runWithCallerWithStrictMode(mPackageContext1, () ->
+ getManager().setDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s2"),
+ makeShortcut("s3"),
+ makeShortcutExcludedFromLauncher("s4"),
+ makeShortcutExcludedFromLauncher("s5")
+ )));
+ Thread.sleep(5000);
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
+ final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+ FLAG_GET_PERSISTED_DATA,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list("s1", "s2", "s3", "s4", "s5"),
+ null);
+ assertWith(ret).haveIds("s1", "s2", "s3", "s4", "s5");
+ });
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ // Verifies pushDynamicShortcuts further persists shortcuts into AppSearch without
+ // removing previous shortcuts when max number of shortcuts is reached.
+ getManager().pushDynamicShortcut(makeShortcut("s6"));
+ });
+ Thread.sleep(5000);
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
+ final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+ FLAG_GET_PERSISTED_DATA,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list("s1", "s2", "s3", "s4", "s5", "s6"),
+ null);
+ assertWith(ret).haveIds("s1", "s2", "s3", "s4", "s5", "s6");
+ });
+ SystemUtil.runShellCommand("cmd shortcut reset-config");
+ }
+
+ public void testRemoveDynamicShortcuts_RemovesShortcutsFromDisk() throws Exception {
+ if (!isAppSearchEnabled()) {
+ return;
+ }
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ final ShortcutManager manager = getManager();
+ manager.setDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s2"),
+ makeShortcutExcludedFromLauncher("s3"),
+ makeShortcutExcludedFromLauncher("s4"),
+ makeShortcutExcludedFromLauncher("s5")
+ ));
+ // Verifies removeDynamicShortcuts removes shortcuts from persistence layer
+ manager.removeDynamicShortcuts(list("s1"));
+ });
+ Thread.sleep(5000);
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
+ final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+ FLAG_GET_PERSISTED_DATA,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list("s1", "s2", "s3", "s4", "s5"),
+ null);
+ assertWith(ret).haveIds("s2", "s3", "s4", "s5");
+ });
+ }
+
+ public void testRemoveLongLivedShortcuts_RemovesShortcutsFromDisk() throws Exception {
+ if (!isAppSearchEnabled()) {
+ return;
+ }
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ final ShortcutManager manager = getManager();
+ manager.setDynamicShortcuts(list(
+ makeShortcutExcludedFromLauncher("s1"),
+ makeShortcut("s2"),
+ makeShortcutExcludedFromLauncher("s3"),
+ makeShortcut("s4"),
+ makeShortcut("s5")
+ ));
+ manager.removeDynamicShortcuts(list("s2"));
+ });
+ Thread.sleep(5000);
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
+ final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+ FLAG_GET_PERSISTED_DATA,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list("s1", "s2", "s3", "s4", "s5"),
+ null);
+ assertWith(ret).haveIds("s1", "s3", "s4", "s5");
+ });
+ }
+
+ public void testDisableShortcuts_RemovesShortcutsFromDisk() throws Exception {
+ if (!isAppSearchEnabled()) {
+ return;
+ }
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ final ShortcutManager manager = getManager();
+ manager.setDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcutExcludedFromLauncher("s2"),
+ makeShortcut("s3"),
+ makeShortcutExcludedFromLauncher("s4"),
+ makeShortcut("s5")
+ ));
+ // Verifies disableShortcuts removes shortcuts from persistence layer
+ manager.disableShortcuts(list("s3"));
+ });
+ Thread.sleep(5000);
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
+ final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+ FLAG_GET_PERSISTED_DATA,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list("s1", "s2", "s3", "s4", "s5"),
+ null);
+ assertWith(ret).haveIds("s1", "s2", "s4", "s5");
+ });
+ }
+
+ public void testUpdateShortcuts_UpdateShortcutsOnDisk() throws Exception {
+ if (!isAppSearchEnabled()) {
+ return;
+ }
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ final ShortcutManager manager = getManager();
+ manager.setDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcutExcludedFromLauncher("s2"),
+ makeShortcut("s3"),
+ makeShortcutExcludedFromLauncher("s4"),
+ makeShortcut("s5")
+ ));
+ // Verifies shortcuts in persistence layer are being updated
+ manager.updateShortcuts(list(makeShortcut("s3", "custom")));
+ });
+ Thread.sleep(5000);
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
+ final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+ FLAG_GET_PERSISTED_DATA,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list("s1", "s2", "s3", "s4", "s5"),
+ null);
+ assertWith(ret)
+ .haveIds("s1", "s2", "s3", "s4", "s5")
+ .forShortcutWithId("s3", si -> {
+ assertEquals("custom", si.getShortLabel());
+ });
+ });
+ }
}
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 d7b506a..79efffb 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
@@ -16,7 +16,6 @@
package android.content.pm.cts.shortcutmanager;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.dumpsysShortcut;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
@@ -33,6 +32,7 @@
import android.content.pm.cts.shortcutmanager.common.ReplyUtil;
import android.os.PersistableBundle;
import android.util.Log;
+
import com.android.compatibility.common.util.CddTest;
import java.util.HashMap;
@@ -43,6 +43,7 @@
private static final String TAG = "ShortcutMRPT";
private static final String SHORTCUT_ID = "s12345";
+ private static final String HIDDEN_SHORTCUT_ID = "s24680";
@CddTest(requirement="[3.8.1/C-2-1],[3.8.1/C-3-1]")
public void testIsRequestPinShortcutSupported() {
@@ -351,6 +352,29 @@
}
}
+ /**
+ * Same as {@link ShortcutManager#requestPinShortcut} except the app has no main activities.
+ */
+ public void testRequestPinShortcutExcludedFromLauncher_ThrowsException() {
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ final ShortcutInfo shortcut = makeShortcutBuilder(HIDDEN_SHORTCUT_ID)
+ .setExcludedFromSurfaces(ShortcutInfo.SURFACE_LAUNCHER)
+ .build();
+
+ Log.i(TAG, "Calling requestPinShortcut...");
+ boolean isIllegalArgumentExceptionThrown = false;
+ try {
+ assertTrue(getManager().requestPinShortcut(shortcut, /* intent sender */ null));
+ } catch (IllegalArgumentException e) {
+ isIllegalArgumentExceptionThrown = true;
+ }
+ assertTrue(isIllegalArgumentExceptionThrown);
+ Log.i(TAG, "Done.");
+ });
+ }
+
// TODO Various other cases (already pinned, etc)
// TODO Various error cases (missing mandatory fields, etc)
}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerThrottlingTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerThrottlingTest.java
index 79b6bd1..d1e83f8 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerThrottlingTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerThrottlingTest.java
@@ -21,10 +21,12 @@
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.resetAllThrottling;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.runCommandForNoOutput;
+import android.Manifest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.cts.shortcutmanager.common.Constants;
import android.content.pm.cts.shortcutmanager.common.ReplyUtil;
+import android.permission.cts.PermissionUtils;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
@@ -64,6 +66,8 @@
protected void setUp() throws Exception {
super.setUp();
+ PermissionUtils.grantPermission(TARGET_PACKAGE, Manifest.permission.POST_NOTIFICATIONS);
+
resetAllThrottling(getInstrumentation());
UiDevice.getInstance(getInstrumentation()).pressHome();
@@ -71,6 +75,12 @@
runCommandForNoOutput(getInstrumentation(), "am force-stop " + TARGET_PACKAGE);
}
+ @Override
+ protected void tearDown() throws Exception {
+ PermissionUtils.revokePermission(TARGET_PACKAGE, Manifest.permission.POST_NOTIFICATIONS);
+ super.tearDown();
+ }
+
public void testSetDynamicShortcuts() throws InterruptedException {
callTest(Constants.TEST_SET_DYNAMIC_SHORTCUTS);
}
diff --git a/tests/tests/slice/src/android/slice/cts/SlicePermissionsTest.java b/tests/tests/slice/src/android/slice/cts/SlicePermissionsTest.java
index c3cf011..0b1387a 100644
--- a/tests/tests/slice/src/android/slice/cts/SlicePermissionsTest.java
+++ b/tests/tests/slice/src/android/slice/cts/SlicePermissionsTest.java
@@ -14,7 +14,6 @@
package android.slice.cts;
-import android.content.pm.PackageManager;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -22,8 +21,12 @@
import static org.junit.Assume.assumeFalse;
import android.app.slice.SliceManager;
+import android.app.slice.SliceProvider;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Process;
@@ -35,6 +38,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
+
@RunWith(AndroidJUnit4.class)
public class SlicePermissionsTest {
@@ -174,6 +179,14 @@
}
@Test
+ public void testPermissionIntent() {
+ Intent intent = SliceProvider.createPermissionIntent(mContext, BASE_URI, mTestPkg);
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> activities = pm.queryIntentActivities(intent, 0);
+ assertEquals(1, activities.size());
+ }
+
+ @Test
public void testRevokeChild() {
assumeFalse(isSliceDisabled);
Uri uri = BASE_URI.buildUpon()
diff --git a/tests/tests/speech/OWNERS b/tests/tests/speech/OWNERS
index c9150c2..e72b790 100644
--- a/tests/tests/speech/OWNERS
+++ b/tests/tests/speech/OWNERS
@@ -1,2 +1,5 @@
# Bug component: 63521
-rni@google.com
\ No newline at end of file
+volnov@google.com
+eugeniom@google.com
+schfan@google.com
+andreaambu@google.com
\ No newline at end of file
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
index 740020c..7377132 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
@@ -18,12 +18,12 @@
import static android.telecom.Connection.PROPERTY_HIGH_DEF_AUDIO;
import static android.telecom.Connection.PROPERTY_WIFI;
-import static android.telecom.cts.TestUtils.*;
-
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static android.telecom.cts.TestUtils.InvokeCounter;
+import static android.telecom.cts.TestUtils.TEST_PHONE_ACCOUNT_HANDLE;
+import static android.telecom.cts.TestUtils.TEST_PHONE_ACCOUNT_HANDLE_2;
+import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS;
import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import android.content.Context;
@@ -299,6 +299,9 @@
mConnection.setConnectionProperties(Connection.PROPERTY_CROSS_SIM);
assertCallProperties(mCall, Call.Details.PROPERTY_CROSS_SIM);
+
+ mConnection.setConnectionProperties(Connection.PROPERTY_TETHERED_CALL);
+ assertCallProperties(mCall, Call.Details.PROPERTY_TETHERED_CALL);
}
/**
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallTest.java b/tests/tests/telecom/src/android/telecom/cts/CallTest.java
index f7bad4e..1659c12 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallTest.java
@@ -125,4 +125,11 @@
assertTrue(cas.getSupportedBluetoothDevices().contains(TestUtils.BLUETOOTH_DEVICE1));
assertTrue(cas.getSupportedBluetoothDevices().contains(TestUtils.BLUETOOTH_DEVICE2));
}
+
+ public void testExternalCallAudioState() {
+ CallAudioState cas = new CallAudioState(false, CallAudioState.ROUTE_EXTERNAL,
+ CallAudioState.ROUTE_BLUETOOTH + CallAudioState.ROUTE_SPEAKER
+ + CallAudioState.ROUTE_EARPIECE);
+ assertEquals(CallAudioState.ROUTE_EXTERNAL, cas.getSupportedRouteMask());
+ }
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/EmergencyCallTests.java b/tests/tests/telecom/src/android/telecom/cts/EmergencyCallTests.java
index a1d9e6d..e91c831 100644
--- a/tests/tests/telecom/src/android/telecom/cts/EmergencyCallTests.java
+++ b/tests/tests/telecom/src/android/telecom/cts/EmergencyCallTests.java
@@ -16,13 +16,19 @@
package android.telecom.cts;
+import android.content.ComponentName;
import android.net.Uri;
import android.os.Bundle;
import android.provider.CallLog;
import android.telecom.Call;
import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
-import java.util.concurrent.CountDownLatch;
+import java.util.ArrayList;
+import java.util.Random;
+import java.util.UUID;
public class EmergencyCallTests extends BaseTelecomTestWithMockServices {
@@ -37,16 +43,49 @@
}
/**
+ * Tests a scenario where an emergency call could fail due to the presence of invalid
+ * {@link PhoneAccount} data.
+ * The seed and quantity for {@link #generateRandomPhoneAccounts(long, int)} is chosen to
+ * represent a set of phone accounts which is known in AOSP to cause a failure placing an
+ * emergency call. {@code 52L} was chosen as a random seed and {@code 50} was chosen as the
+ * set size for {@link PhoneAccount}s as these were observed in repeated test invocations to
+ * induce the failure method.
+ *
+ * @throws Exception
+ */
+ public void testEmergencyCallFailureDueToInvalidPhoneAccounts() throws Exception {
+ if (!mShouldTestTelecom) return;
+
+ ArrayList<PhoneAccount> accounts = generateRandomPhoneAccounts(52L, 50);
+ accounts.stream().forEach(a -> mTelecomManager.registerPhoneAccount(a));
+ try {
+ assertTrue(mTelecomManager.getSelfManagedPhoneAccounts().size() >= 50);
+
+ // The existing start emergency call test is impacted if there is a failure due to
+ // excess phone accounts being present.
+ testStartEmergencyCall();
+ } finally {
+ accounts.stream().forEach(d -> mTelecomManager.unregisterPhoneAccount(
+ d.getAccountHandle()));
+ }
+ }
+
+ /**
* Place an outgoing emergency call and ensure it is started successfully.
*/
public void testStartEmergencyCall() throws Exception {
if (!mShouldTestTelecom) return;
- placeAndVerifyEmergencyCall(true /*supportsHold*/);
+ Connection conn = placeAndVerifyEmergencyCall(true /*supportsHold*/);
Call eCall = getInCallService().getLastCall();
assertCallState(eCall, Call.STATE_DIALING);
assertIsInCall(true);
assertIsInManagedCall(true);
+ conn.setActive();
+ conn.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+ conn.destroy();
+ assertConnectionState(conn, Connection.STATE_DISCONNECTED);
+ assertIsInCall(false);
}
/**
@@ -147,4 +186,44 @@
// Notify as missed instead of rejected, since the user did not explicitly reject.
verifyCallLogging(normalIncomingCallNumber, CallLog.Calls.MISSED_TYPE);
}
+
+ /**
+ * Generates random phone accounts.
+ * @param seed random seed to use for random UUIDs; passed in for determinism.
+ * @param count How many phone accounts to use.
+ * @return Random phone accounts.
+ */
+ private ArrayList<PhoneAccount> generateRandomPhoneAccounts(long seed, int count) {
+ Random random = new Random(seed);
+ ArrayList<PhoneAccount> accounts = new ArrayList<>();
+ for (int ix = 0 ; ix < count; ix++) {
+ ArrayList<String> supportedSchemes = new ArrayList<>();
+ supportedSchemes.add("tel");
+ supportedSchemes.add("sip");
+ supportedSchemes.add("custom");
+ PhoneAccountHandle handle = new PhoneAccountHandle(new ComponentName(TestUtils.PACKAGE,
+ TestUtils.COMPONENT), getRandomUuid(random).toString());
+ PhoneAccount acct = new PhoneAccount.Builder(handle, "TelecommTests")
+ .setAddress(Uri.fromParts("tel", "555-1212", null))
+ .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+ .setHighlightColor(0)
+ .setShortDescription("test_" + ix)
+ .setSupportedUriSchemes(supportedSchemes)
+ .build();
+ accounts.add(acct);
+ }
+ return accounts;
+ }
+
+ /**
+ * Returns a random UUID based on the passed in Random generator.
+ * @param random Random generator.
+ * @return The UUID.
+ */
+ private UUID getRandomUuid(Random random) {
+ byte[] array = new byte[16];
+ random.nextBytes(array);
+ return UUID.nameUUIDFromBytes(array);
+ }
+
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/TestUtils.java b/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
index 0b74a9e..a31e90f 100644
--- a/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
+++ b/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
@@ -335,7 +335,7 @@
}
final PackageManager pm = context.getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) &&
- pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE);
+ pm.hasSystemFeature(PackageManager.FEATURE_TELECOM);
}
public static String setCallDiagnosticService(Instrumentation instrumentation,
diff --git a/tests/tests/telephony/README.md b/tests/tests/telephony/README.md
new file mode 100644
index 0000000..f1f1a32
--- /dev/null
+++ b/tests/tests/telephony/README.md
@@ -0,0 +1,3 @@
+# Telephony CTS tests
+
+This directory contains the main set of Telephony CTS tests.
diff --git a/tests/tests/telephony/current/Android.bp b/tests/tests/telephony/current/Android.bp
index 0d3b179..cccffdb 100644
--- a/tests/tests/telephony/current/Android.bp
+++ b/tests/tests/telephony/current/Android.bp
@@ -32,7 +32,8 @@
"src/android/telephony/ims/cts/ImsUtils.java",
"src/android/telephony/ims/cts/SipDialogAttributes.java",
"src/android/telephony/ims/cts/SipMessageUtils.java",
- "src/android/telephony/ims/cts/TestAcsClient.java",
+ "src/android/telephony/ims/cts/TestAcsClient.java",
+ "src/android/telephony/ims/cts/TestImsCallSessionImpl.java",
],
path: "src/",
}
@@ -75,7 +76,7 @@
local_include_dirs: [
"EmbmsMiddlewareTestApp/aidl/",
"TestExternalImsServiceApp/aidl/",
- ]
+ ],
},
test_suites: [
"cts",
@@ -92,6 +93,5 @@
name: "cts-telephony-utils",
srcs: [
"src/android/telephony/cts/TelephonyUtils.java",
- ]
+ ],
}
-
diff --git a/tests/tests/telephony/current/AndroidManifest.xml b/tests/tests/telephony/current/AndroidManifest.xml
index 5eca91d..8befd97 100644
--- a/tests/tests/telephony/current/AndroidManifest.xml
+++ b/tests/tests/telephony/current/AndroidManifest.xml
@@ -17,6 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.telephony.cts">
+ <uses-permission android:name="android.permission.CALL_PHONE"/>
+ <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS"/>
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
@@ -106,7 +108,7 @@
</intent-filter>
</service>
- <service android:name="android.telephony.cts.StubInCallService"
+ <service android:name="android.telephony.cts.InCallServiceStateValidator"
android:permission="android.permission.BIND_INCALL_SERVICE"
android:exported="true">
<intent-filter>
diff --git a/tests/tests/telephony/current/TestExternalImsServiceApp/aidl/android/telephony/cts/externalimsservice/ITestExternalImsService.aidl b/tests/tests/telephony/current/TestExternalImsServiceApp/aidl/android/telephony/cts/externalimsservice/ITestExternalImsService.aidl
index 2529ada..71924a2 100644
--- a/tests/tests/telephony/current/TestExternalImsServiceApp/aidl/android/telephony/cts/externalimsservice/ITestExternalImsService.aidl
+++ b/tests/tests/telephony/current/TestExternalImsServiceApp/aidl/android/telephony/cts/externalimsservice/ITestExternalImsService.aidl
@@ -29,4 +29,5 @@
boolean isRcsFeatureCreated();
boolean isMmTelFeatureCreated();
void resetState();
+ boolean isTelephonyBound();
}
diff --git a/tests/tests/telephony/current/TestExternalImsServiceApp/src/android/telephony/cts/externalimsservice/TestExternalImsService.java b/tests/tests/telephony/current/TestExternalImsServiceApp/src/android/telephony/cts/externalimsservice/TestExternalImsService.java
index 9101ae5..eaeb6d1 100644
--- a/tests/tests/telephony/current/TestExternalImsServiceApp/src/android/telephony/cts/externalimsservice/TestExternalImsService.java
+++ b/tests/tests/telephony/current/TestExternalImsServiceApp/src/android/telephony/cts/externalimsservice/TestExternalImsService.java
@@ -29,7 +29,7 @@
*/
public class TestExternalImsService extends TestImsService {
- private static final String TAG = "GtsImsTestDeviceImsService";
+ private static final String TAG = "CtsImsTestDeviceImsService";
// TODO: Use ImsService.SERVICE_INTERFACE definition when it becomes public.
private static final String ACTION_BIND_IMS_SERVICE = "android.telephony.ims.ImsService";
@@ -56,19 +56,26 @@
public void resetState() {
TestExternalImsService.this.resetState();
}
+
+ public boolean isTelephonyBound() {
+ return TestExternalImsService.this.isTelephonyBound();
+ }
}
@Override
public IBinder onBind(Intent intent) {
- if (ACTION_BIND_IMS_SERVICE.equals(intent.getAction())) {
- if (ImsUtils.VDBG) {
- Log.i(TAG, "onBind-Remote");
+ synchronized (mLock) {
+ if (ACTION_BIND_IMS_SERVICE.equals(intent.getAction())) {
+ if (ImsUtils.VDBG) {
+ Log.i(TAG, "onBind-Remote");
+ }
+ mIsTelephonyBound = true;
+ return super.onBind(intent);
}
- return super.onBind(intent);
+ if (ImsUtils.VDBG) {
+ Log.i(TAG, "onBind-Local");
+ }
+ return mBinder;
}
- if (ImsUtils.VDBG) {
- Log.i(TAG, "onBind-Local");
- }
- return mBinder;
}
}
diff --git a/tests/tests/telephony/current/permissions/Android.bp b/tests/tests/telephony/current/permissions/Android.bp
deleted file mode 100644
index f13b0e3..0000000
--- a/tests/tests/telephony/current/permissions/Android.bp
+++ /dev/null
@@ -1,40 +0,0 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test {
- name: "CtsTelephonyTestCasesPermissionReadPhoneState",
- defaults: ["cts_defaults"],
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
- ],
- // Include both the 32 and 64 bit versions
- compile_multilib: "both",
- static_libs: [
- "ctstestrunner-axt",
- "compatibility-device-util-axt",
- ],
- srcs: [
- "src/**/*.java",
- ":cts-telephony-utils"
- ],
- sdk_version: "test_current",
-}
diff --git a/tests/tests/telephony/current/permissions/AndroidManifest.xml b/tests/tests/telephony/current/permissions/AndroidManifest.xml
deleted file mode 100644
index 8247dcd..0000000
--- a/tests/tests/telephony/current/permissions/AndroidManifest.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.telephony.cts.telephonypermission" android:targetSandboxVersion="2">
-
- <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-
- <!-- Must be debuggable for compat shell commands to work on user builds -->
- <application android:debuggable="true" />
-
- <!--
- The CTS stubs package cannot be used as the target application here,
- since that requires many permissions to be set. Instead, specify this
- package itself as the target and include any stub activities needed.
-
- This test package uses the default InstrumentationTestRunner, because
- the InstrumentationCtsTestRunner is only available in the stubs
- package. That runner cannot be added to this package either, since it
- relies on hidden APIs.
- -->
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.telephony.cts.telephonypermission"
- android:label="CTS tests of android.permission">
- <meta-data android:name="listener"
- android:value="com.android.cts.runner.CtsTestRunListener" />
- </instrumentation>
-
-</manifest>
-
diff --git a/tests/tests/telephony/current/permissions/AndroidTest.xml b/tests/tests/telephony/current/permissions/AndroidTest.xml
deleted file mode 100644
index b3cce9e..0000000
--- a/tests/tests/telephony/current/permissions/AndroidTest.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT 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 Telephony Permission test cases">
- <option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="framework" />
-
- <!-- Telephony permission tests do not use any permission not available to instant apps. -->
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
- <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-
- <!-- Install main test suite apk -->
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="CtsTelephonyTestCasesPermissionReadPhoneState.apk" />
- </target_preparer>
-
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="android.telephony.cts.telephonypermission" />
- <option name="runtime-hint" value="1m" />
- </test>
-</configuration>
diff --git a/tests/tests/telephony/current/permissions/src/android/telephony/cts/telephonypermission/TelephonyManagerReadPhoneStatePermissionTest.java b/tests/tests/telephony/current/permissions/src/android/telephony/cts/telephonypermission/TelephonyManagerReadPhoneStatePermissionTest.java
deleted file mode 100644
index 7d164d4..0000000
--- a/tests/tests/telephony/current/permissions/src/android/telephony/cts/telephonypermission/TelephonyManagerReadPhoneStatePermissionTest.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony.cts.telephonypermission;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.platform.test.annotations.AppModeFull;
-import android.telecom.PhoneAccount;
-import android.telecom.TelecomManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.telephony.cts.TelephonyUtils;
-import android.telephony.emergency.EmergencyNumber;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.ShellIdentityUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test TelephonyManager APIs with READ_PHONE_STATE Permission.
- */
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Cannot grant the runtime permission in instant app mode")
-public class TelephonyManagerReadPhoneStatePermissionTest {
-
- private boolean mHasTelephony;
- TelephonyManager mTelephonyManager = null;
- TelecomManager mTelecomManager = null;
-
- @Before
- public void setUp() throws Exception {
- mHasTelephony = getContext().getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_TELEPHONY);
- mTelephonyManager =
- (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
- assertNotNull(mTelephonyManager);
- mTelecomManager =
- (TelecomManager) getContext().getSystemService(Context.TELECOM_SERVICE);
- assertNotNull(mTelecomManager);
- }
-
- @After
- public void tearDown() throws Exception {
- TelephonyUtils.resetCompatCommand(InstrumentationRegistry.getInstrumentation(),
- TelephonyUtils.CTS_APP_PACKAGE,
- TelephonyUtils.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION_STRING);
- }
-
- /**
- * Verify that TelephonyManager APIs requiring READ_PHONE_STATE Permission must work.
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#READ_PHONE_STATE}.
- *
- * APIs list:
- * getDeviceSoftwareVersion()
- * getCarrierConfig()
- * getNetworkType()
- * getDataNetworkType()
- * getVoiceNetworkType()
- * getGroupIdLevel1()
- * getLine1AlphaTag()
- * getVoiceMailNumber()
- * getVisualVoicemailPackageName()
- * getVoiceMailAlphaTag()
- * getForbiddenPlmns()
- * isDataRoamingEnabled()
- * getSubscriptionId(@NonNull PhoneAccountHandle phoneAccountHandle)
- * getServiceState()
- * getEmergencyNumberList()
- * getEmergencyNumberList(@EmergencyServiceCategories int categories)
- * getPreferredOpportunisticDataSubscription()
- * isModemEnabledForSlot(int slotIndex)
- * isMultiSimSupported()
- * doesSwitchMultiSimConfigTriggerReboot()
- * getCallState() (when compat fwk enables enforcement)
- * getCallStateForSubscription() (when compat fwk enables enforcement)
- */
- @Test
- public void testTelephonyManagersAPIsRequiringReadPhoneStatePermissions() throws Exception {
- if (!mHasTelephony) {
- return;
- }
-
- try {
- // We must ensure that compat fwk enables READ_PHONE_STATE enforcement
- TelephonyUtils.enableCompatCommand(InstrumentationRegistry.getInstrumentation(),
- TelephonyUtils.CTS_APP_PACKAGE,
- TelephonyUtils.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION_STRING);
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getCallState());
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getCallStateForSubscription());
- } catch (SecurityException e) {
- fail("TelephonyManager#getCallState and TelephonyManager#getCallStateForSubscription "
- + "must not throw a SecurityException because READ_PHONE_STATE permission is "
- + "granted and TelecomManager#ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION is "
- + "enabled.");
- }
-
- int subId = mTelephonyManager.getSubscriptionId();
-
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getNetworkType());
- } catch (SecurityException e) {
- fail("getNetworkType() must not throw a SecurityException with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getDeviceSoftwareVersion());
- } catch (SecurityException e) {
- fail("getDeviceSoftwareVersion() must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getCarrierConfig());
- } catch (SecurityException e) {
- fail("getCarrierConfig() must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getDataNetworkType());
- } catch (SecurityException e) {
- fail("getDataNetworkType() must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getVoiceNetworkType());
- } catch (SecurityException e) {
- fail("getVoiceNetworkType() must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getGroupIdLevel1());
- } catch (SecurityException e) {
- fail("getGroupIdLevel1() must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getLine1AlphaTag());
- } catch (SecurityException e) {
- fail("getLine1AlphaTag() must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getVoiceMailNumber());
- } catch (SecurityException e) {
- fail("getVoiceMailNumber() must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getVisualVoicemailPackageName());
- } catch (SecurityException e) {
- fail("getVisualVoicemailPackageName() must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getVoiceMailAlphaTag());
- } catch (SecurityException e) {
- fail("getVoiceMailAlphaTag() must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getForbiddenPlmns());
- } catch (SecurityException e) {
- fail("getForbiddenPlmns() must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.isDataRoamingEnabled());
- } catch (SecurityException e) {
- fail("isDataRoamingEnabled() must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getSubscriptionId(
- mTelecomManager.getDefaultOutgoingPhoneAccount(
- PhoneAccount.SCHEME_TEL)));
- } catch (SecurityException e) {
- fail("getSubscriptionId(phoneAccountHandle) must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getServiceState());
- } catch (SecurityException e) {
- fail("getServiceState() must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getEmergencyNumberList());
- } catch (SecurityException e) {
- fail("getEmergencyNumberList() must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getEmergencyNumberList(
- EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE));
- } catch (SecurityException e) {
- fail("getEmergencyNumberList(EMERGENCY_SERVICE_CATEGORY_POLICE) must"
- + " not throw a SecurityException with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getPreferredOpportunisticDataSubscription());
- } catch (SecurityException e) {
- fail("getPreferredOpportunisticDataSubscription() must not throw"
- + " a SecurityException with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.isModemEnabledForSlot(
- SubscriptionManager.getSlotIndex(subId)));
- } catch (SecurityException e) {
- fail("isModemEnabledForSlot(slotIndex) must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.isMultiSimSupported());
- } catch (SecurityException e) {
- fail("isMultiSimSupported() must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- try {
- ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.doesSwitchMultiSimConfigTriggerReboot());
- } catch (SecurityException e) {
- fail("doesSwitchMultiSimConfigTriggerReboot() must not throw a SecurityException"
- + " with READ_PHONE_STATE" + e);
- }
- }
-
- private static Context getContext() {
- return InstrumentationRegistry.getContext();
- }
-}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/ApnSettingTest.java b/tests/tests/telephony/current/src/android/telephony/cts/ApnSettingTest.java
index 1be3eb8..bb25a89 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/ApnSettingTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/ApnSettingTest.java
@@ -42,6 +42,8 @@
EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_EMERGENCY_STRING, ApnSetting.TYPE_EMERGENCY);
EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_MCX_STRING, ApnSetting.TYPE_MCX);
EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_XCAP_STRING, ApnSetting.TYPE_XCAP);
+ EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_ENTERPRISE_STRING,
+ ApnSetting.TYPE_ENTERPRISE);
EXPECTED_INT_TO_STRING_MAP = new ArrayMap<>();
EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_DEFAULT, ApnSetting.TYPE_DEFAULT_STRING);
@@ -56,6 +58,8 @@
EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_EMERGENCY, ApnSetting.TYPE_EMERGENCY_STRING);
EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_MCX, ApnSetting.TYPE_MCX_STRING);
EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_XCAP, ApnSetting.TYPE_XCAP_STRING);
+ EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_ENTERPRISE,
+ ApnSetting.TYPE_ENTERPRISE_STRING);
}
@Test
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
index e62adc5..542d687 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
@@ -35,7 +35,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-
+import static org.junit.Assume.assumeTrue;
import android.app.UiAutomation;
import android.content.BroadcastReceiver;
@@ -70,7 +70,6 @@
private CarrierConfigManager mConfigManager;
private TelephonyManager mTelephonyManager;
private SubscriptionManager mSubscriptionManager;
- private PackageManager mPackageManager;
// Use a long timeout to accommodate devices with lower amounts of memory, as it will take
// longer for these devices to receive the broadcast (b/161963269). It is expected that all
@@ -80,6 +79,8 @@
@Before
public void setUp() throws Exception {
+ assumeTrue(getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
mTelephonyManager = (TelephonyManager)
getContext().getSystemService(Context.TELEPHONY_SERVICE);
mConfigManager = (CarrierConfigManager)
@@ -87,7 +88,6 @@
mSubscriptionManager =
(SubscriptionManager)
getContext().getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
- mPackageManager = getContext().getPackageManager();
}
@After
@@ -174,6 +174,14 @@
assertEquals("KEY_CARRIER_USSD_METHOD_INT doesn't match static default.",
config.getInt(CarrierConfigManager.KEY_CARRIER_USSD_METHOD_INT),
CarrierConfigManager.USSD_OVER_CS_PREFERRED);
+ assertEquals("KEY_USAGE_SETTING_INT doesn't match static default.",
+ config.getInt(CarrierConfigManager.KEY_CELLULAR_USAGE_SETTING_INT),
+ SubscriptionManager.USAGE_SETTING_UNKNOWN);
+ assertEquals("KEY_ENABLE_CROSS_SIM_CALLING_ON_OPPORTUNISTIC_DATA_BOOL"
+ + " doesn't match static default.",
+ config.getBoolean(
+ CarrierConfigManager.KEY_ENABLE_CROSS_SIM_CALLING_ON_OPPORTUNISTIC_DATA_BOOL),
+ false);
}
// These key should return default values if not customized.
@@ -204,9 +212,6 @@
@Test
@AsbSecurityTest(cveBugId = 73136824)
public void testRevokePermission() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
PersistableBundle config;
try {
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java
index bae1f29..ea5d1ee 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java
@@ -45,23 +45,27 @@
}
}
+ @Override
+ protected void runTest() throws Throwable {
+ if (!hasCellular()) {
+ return;
+ }
+ super.runTest();
+ }
+
private static boolean hasCellular() {
PackageManager packageManager = getInstrumentation().getContext().getPackageManager();
TelephonyManager telephonyManager =
getInstrumentation().getContext().getSystemService(TelephonyManager.class);
- return packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+ return packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
&& telephonyManager.getPhoneCount() > 0;
}
public void testNotifyCarrierNetworkChange_true() {
- if (!mHasCellular) return;
-
notifyCarrierNetworkChange(true);
}
public void testNotifyCarrierNetworkChange_false() {
- if (!mHasCellular) return;
-
notifyCarrierNetworkChange(false);
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/IRadioModemImpl.java b/tests/tests/telephony/current/src/android/telephony/cts/IRadioModemImpl.java
index 44a7ee5..af7a25c 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/IRadioModemImpl.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/IRadioModemImpl.java
@@ -26,6 +26,8 @@
import android.os.RemoteException;
import android.util.Log;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_RADIO_POWER;
+
public class IRadioModemImpl extends IRadioModem.Stub {
private static final String TAG = "MRMDM";
@@ -39,6 +41,8 @@
private IRadioModemResponse mRadioModemResponse;
private IRadioModemIndication mRadioModemIndication;
+ private int mForceRadioPowerError = -1;
+
public IRadioModemImpl(MockModemService service) {
Log.d(TAG, "Instantiated");
@@ -268,10 +272,17 @@
boolean forEmergencyCall,
boolean preferredForEmergencyCall) {
Log.d(TAG, "setRadioPower");
+ RadioResponseInfo rsp = null;
// TODO: cache value
- RadioResponseInfo rsp = mService.makeSolRsp(serial);
+ // Check if the error response needs to be modified
+ if (mForceRadioPowerError != -1) {
+ rsp = mService.makeSolRsp(serial, mForceRadioPowerError);
+ } else {
+ rsp = mService.makeSolRsp(serial);
+ }
+
try {
mRadioModemResponse.setRadioPowerResponse(rsp);
} catch (RemoteException ex) {
@@ -279,12 +290,14 @@
}
// TODO: The below should be handled by Helper function
- if (powerOn) {
- radioStateChanged(RadioState.ON);
- mService.countDownLatch(MockModemService.LATCH_MOCK_MODEM_RADIO_POWR_ON);
- } else {
- radioStateChanged(RadioState.OFF);
- mService.countDownLatch(MockModemService.LATCH_MOCK_MODEM_RADIO_POWR_OFF);
+ if (rsp.error == RadioError.NONE) {
+ if (powerOn) {
+ radioStateChanged(RadioState.ON);
+ mService.countDownLatch(MockModemService.LATCH_MOCK_MODEM_RADIO_POWR_ON);
+ } else {
+ radioStateChanged(RadioState.OFF);
+ mService.countDownLatch(MockModemService.LATCH_MOCK_MODEM_RADIO_POWR_OFF);
+ }
}
}
@@ -347,4 +360,14 @@
Log.e(TAG, "null mRadioModemIndication");
}
}
-}
+
+ public void forceErrorResponse(int requestId, int error) {
+ switch (requestId) {
+ case RIL_REQUEST_RADIO_POWER:
+ mForceRadioPowerError = error;
+ break;
+ default:
+ break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/InCallServiceStateValidator.java b/tests/tests/telephony/current/src/android/telephony/cts/InCallServiceStateValidator.java
new file mode 100644
index 0000000..53b1016
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/InCallServiceStateValidator.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts;
+
+import android.content.Intent;
+import android.telecom.Call;
+import android.telecom.InCallService;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+public class InCallServiceStateValidator extends InCallService {
+
+ private static final String LOG_TAG = "InCallServiceStateValidator";
+ private static final Object sLock = new Object();
+ private static InCallServiceCallbacks sCallbacks;
+ private static CountDownLatch sLatch = new CountDownLatch(1);
+
+ private final List<Call> mCalls = Collections.synchronizedList(new ArrayList<>());
+ private final Object mLock = new Object();
+ private boolean mIsServiceBound = false;
+
+ public static class InCallServiceCallbacks {
+ private InCallServiceStateValidator mService = null;
+ public Semaphore lock = new Semaphore(0);
+
+ public void onCallAdded(Call call, int numCalls) {};
+ public void onCallRemoved(Call call, int numCalls) {};
+ public void onCallStateChanged(Call call, int state) {};
+
+ public InCallServiceStateValidator getService() {
+ if (mService == null) {
+ try {
+ sLatch.await(3000, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Log.d(LOG_TAG, "InterruptedException");
+ }
+ sLatch = new CountDownLatch(1);
+ }
+ return mService;
+ }
+
+ public void setService(InCallServiceStateValidator service) {
+ mService = service;
+ }
+
+ public void resetLock() {
+ lock = new Semaphore(0);
+ }
+ }
+
+ public static void setCallbacks(InCallServiceCallbacks callbacks) {
+ synchronized (sLock) {
+ sCallbacks = callbacks;
+ }
+ }
+
+ private InCallServiceCallbacks getCallbacks() {
+ synchronized (sLock) {
+ if (sCallbacks != null) {
+ sCallbacks.setService(this);
+ }
+ return sCallbacks;
+ }
+ }
+
+ /**
+ * Note that the super implementations of the callback methods are all no-ops, but we call
+ * them anyway to make sure that the CTS coverage tool detects that we are testing them.
+ */
+ private Call.Callback mCallCallback = new Call.Callback() {
+ @Override
+ public void onStateChanged(Call call, int state) {
+ super.onStateChanged(call, state);
+ if (getCallbacks() != null) {
+ getCallbacks().onCallStateChanged(call, state);
+ }
+ }
+ };
+
+ @Override
+ public android.os.IBinder onBind(android.content.Intent intent) {
+ if (getCallbacks() != null) {
+ getCallbacks().setService(this);
+ }
+ mIsServiceBound = true;
+ sLatch.countDown();
+ return super.onBind(intent);
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ mIsServiceBound = false;
+ mCalls.clear();
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public void onCallAdded(Call call) {
+ super.onCallAdded(call);
+ if (!mCalls.contains(call)) {
+ mCalls.add(call);
+ call.registerCallback(mCallCallback);
+ }
+
+ if (getCallbacks() != null) {
+ getCallbacks().onCallAdded(call, mCalls.size());
+ }
+ }
+
+ @Override
+ public void onCallRemoved(Call call) {
+ super.onCallRemoved(call);
+ mCalls.remove(call);
+
+ if (getCallbacks() != null) {
+ getCallbacks().onCallRemoved(call, mCalls.size());
+ }
+ call.unregisterCallback(mCallCallback);
+ }
+
+ /**
+ * @return the number of calls currently added to the {@code InCallService}.
+ */
+ public int getCallCount() {
+ return mCalls.size();
+ }
+
+ public boolean isServiceBound() {
+ return mIsServiceBound;
+ }
+
+ public boolean isServiceUnBound() {
+ return !mIsServiceBound;
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java b/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
index 57b5a84..fe9cf2c 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import android.app.Activity;
import android.app.PendingIntent;
@@ -94,17 +95,18 @@
private Random mRandom;
private SentReceiver mSentReceiver;
private TelephonyManager mTelephonyManager;
- private PackageManager mPackageManager;
private static class SentReceiver extends BroadcastReceiver {
private final Object mLock;
private boolean mSuccess;
private boolean mDone;
+ private int mExpectedErrorResultCode;
- public SentReceiver() {
+ SentReceiver(int expectedErrorResultCode) {
mLock = new Object();
mSuccess = false;
mDone = false;
+ mExpectedErrorResultCode = expectedErrorResultCode;
}
@Override
@@ -135,6 +137,9 @@
}
} else {
Log.e(TAG, "Failure result=" + resultCode);
+ if (resultCode == mExpectedErrorResultCode) {
+ mSuccess = true;
+ }
if (resultCode == SmsManager.MMS_ERROR_HTTP_FAILURE) {
final int httpError = intent.getIntExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, 0);
Log.e(TAG, "HTTP failure=" + httpError);
@@ -161,7 +166,6 @@
Log.i(TAG, "Wait for sent: done=" + mDone + ", success=" + mSuccess);
return mDone && mSuccess;
}
-
}
}
@@ -170,22 +174,30 @@
mRandom = new Random();
mTelephonyManager =
(TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
- mPackageManager = getContext().getPackageManager();
+ assumeTrue(getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_MESSAGING));
}
@Test
public void testSendMmsMessage() {
- sendMmsMessage(0L /* messageId */);
+ sendMmsMessage(0L /* messageId */, Activity.RESULT_OK, SmsManager.getDefault());
+ }
+
+ @Test
+ public void testSendMmsMessageWithInactiveSubscriptionId() {
+ int inactiveSubId = 127;
+ sendMmsMessage(0L /* messageId */, SmsManager.MMS_ERROR_INACTIVE_SUBSCRIPTION,
+ SmsManager.getSmsManagerForSubscriptionId(inactiveSubId));
}
@Test
public void testSendMmsMessageWithMessageId() {
- sendMmsMessage(MESSAGE_ID);
+ sendMmsMessage(MESSAGE_ID, Activity.RESULT_OK, SmsManager.getDefault());
}
- private void sendMmsMessage(long messageId) {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
- || !doesSupportMMS()) {
+ private void sendMmsMessage(long messageId, int expectedErrorResultCode,
+ SmsManager smsManager) {
+ if (!doesSupportMMS()) {
Log.i(TAG, "testSendMmsMessage skipped: no telephony available or MMS not supported");
return;
}
@@ -194,7 +206,7 @@
final Context context = getContext();
// Register sent receiver
- mSentReceiver = new SentReceiver();
+ mSentReceiver = new SentReceiver(expectedErrorResultCode);
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";
@@ -213,14 +225,15 @@
final PendingIntent pendingIntent = PendingIntent.getBroadcast(
context, 0, new Intent(ACTION_MMS_SENT), PendingIntent.FLAG_MUTABLE);
if (messageId == 0L) {
- SmsManager.getDefault().sendMultimediaMessage(context,
+ smsManager.sendMultimediaMessage(context,
contentUri, null/*locationUrl*/, null/*configOverrides*/, pendingIntent);
} else {
- SmsManager.getDefault().sendMultimediaMessage(context,
+ smsManager.sendMultimediaMessage(context,
contentUri, null/*locationUrl*/, null/*configOverrides*/, pendingIntent,
messageId);
}
assertTrue(mSentReceiver.waitForSuccess(SENT_TIMEOUT));
+ assertTrue(mSentReceiver.getResultCode() == expectedErrorResultCode);
sendFile.delete();
}
@@ -347,8 +360,7 @@
}
private void downloadMultimediaMessage(long messageId) {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
- || !doesSupportMMS()) {
+ if (!doesSupportMMS()) {
Log.i(TAG, "testSendMmsMessage skipped: no telephony available or MMS not supported");
return;
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/MockModemService.java b/tests/tests/telephony/current/src/android/telephony/cts/MockModemService.java
index 4b09895..543469a 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/MockModemService.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/MockModemService.java
@@ -29,6 +29,8 @@
import androidx.test.platform.app.InstrumentationRegistry;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_RADIO_POWER;
+
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -310,4 +312,24 @@
sIRadioSimImpl.setSimPresent(slotId);
sIRadioConfigImpl.setSimPresent(slotId);
}
+
+ /**
+ * Change the response error per specific RIL request
+ *
+ * @param requestId the request/response message ID
+ * @param error RIL_Errno and -1 means to disable the modifed mechanism,
+ * back to original mock modem behavior
+ */
+ public void forceErrorResponse(int requestId, int error) {
+ Log.d(TAG, "setReturnResponseError for request:" + requestId + " ,error:" + error);
+
+ switch (requestId) {
+ case RIL_REQUEST_RADIO_POWER:
+ sIRadioModemImpl.forceErrorResponse(requestId, error);
+ break;
+ default:
+ Log.e(TAG, "request:" + requestId + " not support to change the response error");
+ break;
+ }
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
index 8a13736..5ee3d44 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
@@ -112,7 +112,6 @@
private static final String FINANCIAL_SMS_APP = "android.telephony.cts.financialsms";
private TelephonyManager mTelephonyManager;
- private PackageManager mPackageManager;
private String mDestAddr;
private String mText;
private SmsBroadcastReceiver mSendReceiver;
@@ -139,24 +138,19 @@
@Before
public void setUp() throws Exception {
- assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+ assumeTrue(getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_MESSAGING));
mContext = getContext();
mTelephonyManager =
(TelephonyManager) getContext().getSystemService(
Context.TELEPHONY_SERVICE);
- mPackageManager = mContext.getPackageManager();
mDestAddr = mTelephonyManager.getLine1Number();
mText = "This is a test message";
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- mDeliveryReportSupported = false;
- } else {
- // exclude the networks that don't support SMS delivery report
- String mccmnc = mTelephonyManager.getSimOperator();
- mDeliveryReportSupported = !(CarrierCapability.NO_DELIVERY_REPORTS.contains(mccmnc));
- }
+ // exclude the networks that don't support SMS delivery report
+ String mccmnc = mTelephonyManager.getSimOperator();
+ mDeliveryReportSupported = !(CarrierCapability.NO_DELIVERY_REPORTS.contains(mccmnc));
// register receivers
mSendIntent = new Intent(SMS_SEND_ACTION);
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
index 615c2a8..c7065b7 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
@@ -26,6 +26,8 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -88,18 +90,18 @@
@Before
public void setUp() throws Exception {
+ assumeTrue(getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_MESSAGING));
+ mPackageManager = getContext().getPackageManager();
mTelephonyManager =
(TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
- mPackageManager = getContext().getPackageManager();
}
@Test
public void testCreateFromPdu() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
- || mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA)) {
- // TODO: temp workaround, need to adjust test to use CDMA pdus
- return;
- }
+ // TODO: temp workaround, need to adjust test to use CDMA pdus
+ assumeFalse(mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_CDMA));
String pdu = "07916164260220F0040B914151245584F600006060605130308A04D4F29C0E";
SmsMessage sms = SmsMessage.createFromPdu(hexStringToByteArray(pdu),
@@ -179,11 +181,9 @@
@Test
public void testCPHSVoiceMail() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
- || mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA)) {
- // TODO: temp workaround, need to adjust test to use CDMA pdus
- return;
- }
+ // TODO: temp workaround, need to adjust test to use CDMA pdus
+ assumeFalse(mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_CDMA));
// "set MWI flag"
String pdu = "07912160130310F20404D0110041006060627171118A0120";
@@ -223,11 +223,9 @@
@Test
public void testGetUserData() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
- || mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA)) {
- // TODO: temp workaround, need to adjust test to use CDMA pdus
- return;
- }
+ // TODO: temp workaround, need to adjust test to use CDMA pdus
+ assumeFalse(mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_CDMA));
String pdu = "07914140279510F6440A8111110301003BF56080207130138A8C0B05040B8423F"
+ "000032A02010106276170706C69636174696F6E2F766E642E7761702E6D6D732D"
@@ -243,10 +241,6 @@
@Test
public void testGetSubmitPdu() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
-
SmsMessage.SubmitPdu smsPdu;
String scAddress = null, destinationAddress = null;
String message = null;
@@ -284,11 +278,9 @@
@Test
public void testEmailGateway() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
- || mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA)) {
- // TODO: temp workaround, need to adjust test to use CDMA pdus
- return;
- }
+ // TODO: temp workaround, need to adjust test to use CDMA pdus
+ assumeFalse(mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_CDMA));
String pdu = "07914151551512f204038105f300007011103164638a28e6f71b50c687db" +
"7076d9357eb7412f7a794e07cdeb6275794c07bde8e5391d247e93f3";
@@ -317,10 +309,6 @@
@Test
public void testCalculateLength() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
-
int[] result = SmsMessage.calculateLength(LONG_TEXT_WITH_32BIT_CHARS, false);
assertEquals(6, result.length);
assertEquals(3, result[0]);
@@ -343,19 +331,12 @@
@Test
public void testCalculateLengthFlags() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
int[] result = SmsMessage.calculateLength(LONG_TEXT_WITH_FLAGS, false);
assertEquals(2, result[0]);
}
@Test
public void testGetSmsPdu() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
-
SmsMessage.SubmitPdu smsPdu;
String scAddress = null;
String destinationAddress = null;
@@ -391,9 +372,6 @@
@Test
public void testGetSubmitPduEncodedMessage() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
String destinationAddress = "18004664411";
String message = "This is a test message";
@@ -433,9 +411,6 @@
@Test
public void testCreateFromNativeSmsSubmitPdu() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
// Short message with status RECEIVED_READ and size 0. See 3GPP2 C.S0023 3.4.27
byte[] submitPdu = {1, 0};
SmsMessage sms = SmsMessage.createFromNativeSmsSubmitPdu(submitPdu, true);
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsUsageMonitorShortCodeTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsUsageMonitorShortCodeTest.java
index 32c4258..def4335 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsUsageMonitorShortCodeTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsUsageMonitorShortCodeTest.java
@@ -17,25 +17,23 @@
package android.telephony.cts;
import static android.telephony.SmsManager.SMS_CATEGORY_FREE_SHORT_CODE;
-import static android.telephony.SmsManager.SMS_CATEGORY_STANDARD_SHORT_CODE;
import static android.telephony.SmsManager.SMS_CATEGORY_NOT_SHORT_CODE;
import static android.telephony.SmsManager.SMS_CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE;
import static android.telephony.SmsManager.SMS_CATEGORY_PREMIUM_SHORT_CODE;
+import static android.telephony.SmsManager.SMS_CATEGORY_STANDARD_SHORT_CODE;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.telephony.SmsManager;
-import android.test.InstrumentationTestCase;
import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsManager;
import androidx.test.annotation.UiThreadTest;
-import com.android.internal.telephony.SmsUsageMonitor;
-
import org.junit.Before;
import org.junit.Test;
@@ -44,7 +42,6 @@
*/
public class SmsUsageMonitorShortCodeTest {
- private PackageManager mPackageManager;
private Context mContext;
private static final class ShortCodeTest {
@@ -475,7 +472,8 @@
@Before
public void setUp() throws Exception {
mContext = getInstrumentation().getTargetContext();
- mPackageManager = mContext.getPackageManager();
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_MESSAGING));
}
private static int expectedReturnCode(String address) {
@@ -486,11 +484,6 @@
@UiThreadTest
@Test
public void testSmsShortCodeDestination() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- // do not test if device does not support telephony (voice or sms)
- return;
- }
-
for (ShortCodeTest test : sShortCodeTests) {
// It is intended that a short code number in country A may be an emergency number
// in country B. It is intended that the destination will be changed because of this
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/StubInCallService.java b/tests/tests/telephony/current/src/android/telephony/cts/StubInCallService.java
deleted file mode 100644
index 51491d2..0000000
--- a/tests/tests/telephony/current/src/android/telephony/cts/StubInCallService.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.
- */
-
-package android.telephony.cts;
-
-import android.telecom.InCallService;
-
-/**
- * A in call service that does nothing except allowing CTS telephony to be set as a default dialer
- */
-public class StubInCallService extends InCallService {
-
-}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
index 5335f96..041c133 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -30,9 +30,14 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import android.annotation.Nullable;
import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.ConnectivityManager;
@@ -64,6 +69,7 @@
import com.android.compatibility.common.util.TestThread;
import com.android.internal.util.ArrayUtils;
+import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -107,6 +113,51 @@
private int mDefaultVoiceSubId;
private String mPackageName;
private SubscriptionManager mSm;
+ private SubscriptionManagerTest.CarrierConfigReceiver mReceiver;
+
+ private static class CarrierConfigReceiver extends BroadcastReceiver {
+ private CountDownLatch mLatch = new CountDownLatch(1);
+ private final int mSubId;
+
+ CarrierConfigReceiver(int subId) {
+ mSubId = subId;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+ int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ if (mSubId == subId) {
+ mLatch.countDown();
+ }
+ }
+ }
+
+ void clearQueue() {
+ mLatch = new CountDownLatch(1);
+ }
+
+ void waitForCarrierConfigChanged() throws Exception {
+ mLatch.await(5000, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ private void overrideCarrierConfig(PersistableBundle bundle, int subId) throws Exception {
+ mReceiver = new CarrierConfigReceiver(subId);
+ IntentFilter filter = new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+ // ACTION_CARRIER_CONFIG_CHANGED is sticky, so we will get a callback right away.
+ InstrumentationRegistry.getContext().registerReceiver(mReceiver, filter);
+ mReceiver.waitForCarrierConfigChanged();
+ mReceiver.clearQueue();
+
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+ InstrumentationRegistry.getContext().getSystemService(CarrierConfigManager.class),
+ (cm) -> cm.overrideConfig(subId, bundle));
+ mReceiver.waitForCarrierConfigChanged();
+ InstrumentationRegistry.getContext().unregisterReceiver(mReceiver);
+ mReceiver = null;
+ }
/**
* Callback used in testRegisterNetworkCallback that allows caller to block on
@@ -155,12 +206,21 @@
@Before
public void setUp() throws Exception {
- if (!isSupported()) return;
+ assumeTrue(isSupported());
mSm = InstrumentationRegistry.getContext().getSystemService(SubscriptionManager.class);
mSubId = SubscriptionManager.getDefaultDataSubscriptionId();
mDefaultVoiceSubId = SubscriptionManager.getDefaultVoiceSubscriptionId();
mPackageName = InstrumentationRegistry.getContext().getPackageName();
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mReceiver != null) {
+ InstrumentationRegistry.getContext().unregisterReceiver(mReceiver);
+ mReceiver = null;
+ }
}
/**
@@ -170,34 +230,25 @@
*/
@Test
public void testCorrectness() throws Exception {
- if (!isSupported()) return;
-
final boolean hasCellular = findCellularNetwork() != null;
- if (isSupported() && !hasCellular) {
+ if (!hasCellular) {
fail("Device claims to support " + PackageManager.FEATURE_TELEPHONY
+ " but has no active cellular network, which is required for validation");
- } else if (!isSupported() && hasCellular) {
- fail("Device has active cellular network, but claims to not support "
- + PackageManager.FEATURE_TELEPHONY);
}
- if (isSupported()) {
- if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
- fail("Device must have a valid default data subId for validation");
- }
+ if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ fail("Device must have a valid default data subId for validation");
}
}
@Test
public void testGetActiveSubscriptionInfoCount() throws Exception {
- if (!isSupported()) return;
assertTrue(mSm.getActiveSubscriptionInfoCount() <=
mSm.getActiveSubscriptionInfoCountMax());
}
@Test
public void testGetActiveSubscriptionInfoForIcc() throws Exception {
- if (!isSupported()) return;
SubscriptionInfo info = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
(sm) -> sm.getActiveSubscriptionInfo(mSubId));
assertNotNull(ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
@@ -206,13 +257,11 @@
@Test
public void testIsActiveSubscriptionId() throws Exception {
- if (!isSupported()) return;
assertTrue(mSm.isActiveSubscriptionId(mSubId));
}
@Test
public void testGetSubscriptionIds() throws Exception {
- if (!isSupported()) return;
int slotId = SubscriptionManager.getSlotIndex(mSubId);
int[] subIds = mSm.getSubscriptionIds(slotId);
assertNotNull(subIds);
@@ -221,7 +270,6 @@
@Test
public void testGetResourcesForSubId() {
- if (!isSupported()) return;
Resources r = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
(sm) -> sm.getResourcesForSubId(InstrumentationRegistry.getContext(), mSubId));
// this is an old method which returns mcc/mnc as ints, so use the old SM.getMcc/Mnc methods
@@ -232,14 +280,11 @@
@Test
public void testIsUsableSubscriptionId() throws Exception {
- if (!isSupported()) return;
assertTrue(SubscriptionManager.isUsableSubscriptionId(mSubId));
}
@Test
public void testActiveSubscriptions() throws Exception {
- if (!isSupported()) return;
-
List<SubscriptionInfo> subList = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
(sm) -> sm.getActiveSubscriptionInfoList());
int[] idList = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
@@ -265,8 +310,6 @@
@Test
public void testSubscriptionPlans() throws Exception {
- if (!isSupported()) return;
-
// Make ourselves the owner
setSubPlanOwner(mSubId, mPackageName);
@@ -295,8 +338,6 @@
@Test
public void testSubscriptionPlansOverrideCongested() throws Exception {
- if (!isSupported()) return;
-
final ConnectivityManager cm = InstrumentationRegistry.getContext()
.getSystemService(ConnectivityManager.class);
final Network net = findCellularNetwork();
@@ -351,7 +392,7 @@
@Test
public void testSubscriptionInfoRecord() {
- if (!isSupported() || !isAutomotive()) return;
+ if (!isAutomotive()) return;
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
uiAutomation.adoptShellPermissionIdentity();
@@ -380,8 +421,6 @@
@Test
public void testSetDefaultVoiceSubId() {
- if (!isSupported()) return;
-
int oldSubId = SubscriptionManager.getDefaultVoiceSubscriptionId();
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity();
@@ -399,8 +438,6 @@
@Test
public void testSubscriptionPlansOverrideUnmetered() throws Exception {
- if (!isSupported()) return;
-
final ConnectivityManager cm = InstrumentationRegistry.getContext()
.getSystemService(ConnectivityManager.class);
final Network net = findCellularNetwork();
@@ -436,8 +473,6 @@
@Test
public void testSubscriptionPlansUnmetered() throws Exception {
- if (!isSupported()) return;
-
final ConnectivityManager cm = InstrumentationRegistry.getContext()
.getSystemService(ConnectivityManager.class);
final Network net = findCellularNetwork();
@@ -485,8 +520,6 @@
@Test
public void testSubscriptionPlansInvalid() throws Exception {
- if (!isSupported()) return;
-
// Make ourselves the owner
setSubPlanOwner(mSubId, mPackageName);
@@ -526,8 +559,6 @@
@Test
public void testSubscriptionPlansNetworkTypeValidation() throws Exception {
- if (!isSupported()) return;
-
// Make ourselves the owner
setSubPlanOwner(mSubId, mPackageName);
@@ -584,8 +615,6 @@
@Test
public void testSubscriptionGrouping() throws Exception {
- if (!isSupported()) return;
-
// Set subscription group with current sub Id. This should fail
// because we don't have MODIFY_PHONE_STATE or carrier privilege permission.
List<Integer> subGroup = new ArrayList();
@@ -629,8 +658,6 @@
@Test
public void testSubscriptionGroupingWithPermission() throws Exception {
- if (!isSupported()) return;
-
// Set subscription group with current sub Id.
List<Integer> subGroup = new ArrayList();
subGroup.add(mSubId);
@@ -686,8 +713,6 @@
@Test
public void testAddSubscriptionIntoNewGroupWithPermission() throws Exception {
- if (!isSupported()) return;
-
// Set subscription group with current sub Id.
List<Integer> subGroup = new ArrayList();
subGroup.add(mSubId);
@@ -718,8 +743,6 @@
@Test
public void testSettingOpportunisticSubscription() throws Exception {
- if (!isSupported()) return;
-
// Set subscription to be opportunistic. This should fail
// because we don't have MODIFY_PHONE_STATE or carrier privilege permission.
try {
@@ -735,8 +758,6 @@
@Test
public void testMccMncString() {
- if (!isSupported()) return;
-
SubscriptionInfo info = mSm.getActiveSubscriptionInfo(mSubId);
String mcc = info.getMccString();
String mnc = info.getMncString();
@@ -746,8 +767,6 @@
@Test
public void testSetUiccApplicationsEnabled() throws Exception {
- if (!isSupported()) return;
-
boolean canDisable = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
(sm) -> sm.canDisablePhysicalSubscription());
if (canDisable) {
@@ -825,8 +844,6 @@
@Test
public void testSubscriptionInfoCarrierId() {
- if (!isSupported()) return;
-
SubscriptionInfo info = mSm.getActiveSubscriptionInfo(mSubId);
int carrierId = info.getCarrierId();
assertTrue(carrierId >= TelephonyManager.UNKNOWN_CARRIER_ID);
@@ -834,8 +851,6 @@
@Test
public void testGetOpportunisticSubscriptions() throws Exception {
- if (!isSupported()) return;
-
List<SubscriptionInfo> infoList = mSm.getOpportunisticSubscriptions();
for (SubscriptionInfo info : infoList) {
@@ -845,7 +860,6 @@
@Test
public void testGetEnabledSubscriptionId() {
- if (!isSupported()) return;
int slotId = SubscriptionManager.getSlotIndex(mSubId);
if (!SubscriptionManager.isValidSlotIndex(slotId)) {
fail("Invalid slot id " + slotId + " for subscription id " + mSubId);
@@ -857,7 +871,6 @@
@Test
public void testSetAndCheckSubscriptionEnabled() {
- if (!isSupported()) return;
boolean enabled = executeWithShellPermissionAndDefault(false, mSm,
(sm) -> sm.isSubscriptionEnabled(mSubId));
@@ -946,8 +959,6 @@
@Test
public void testGetActiveDataSubscriptionId() {
- if (!isSupported()) return;
-
int activeDataSubIdCurrent = executeWithShellPermissionAndDefault(
SubscriptionManager.INVALID_SUBSCRIPTION_ID, mSm,
(sm) -> sm.getActiveDataSubscriptionId());
@@ -962,7 +973,6 @@
@Test
public void testSetPreferredDataSubscriptionId() {
- if (!isSupported()) return;
int preferredSubId = executeWithShellPermissionAndDefault(-1, mSm,
(sm) -> sm.getPreferredDataSubscriptionId());
if (preferredSubId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
@@ -984,8 +994,6 @@
@Test
public void testRestoreAllSimSpecificSettingsFromBackup() throws Exception {
- if (!isSupported()) return;
-
int activeDataSubId = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
(sm) -> sm.getActiveDataSubscriptionId());
assertNotEquals(activeDataSubId, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
@@ -1089,8 +1097,6 @@
@Test
public void testSetAndGetD2DStatusSharing() {
- if (!isSupported()) return;
-
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
uiAutomation.adoptShellPermissionIdentity(MODIFY_PHONE_STATE);
int originalD2DStatusSharing = mSm.getDeviceToDeviceStatusSharingPreference(mSubId);
@@ -1107,8 +1113,6 @@
@Test
public void testSetAndGetD2DSharingContacts() {
- if (!isSupported()) return;
-
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
uiAutomation.adoptShellPermissionIdentity(MODIFY_PHONE_STATE);
List<Uri> originalD2DSharingContacts = mSm.getDeviceToDeviceStatusSharingContacts(mSubId);
@@ -1120,8 +1124,6 @@
@Test
public void tetsSetAndGetPhoneNumber() throws Exception {
- if (!isSupported()) return;
-
// The phone number may be anything depends on the state of SIM and device.
// Simply call the getter and make sure no exception.
@@ -1205,6 +1207,48 @@
}
}
+ @Test
+ public void testCellularUsageSetting() throws Exception {
+ if (!isSupported()) return;
+
+ boolean isUsageSettingSupported = true;
+ int defaultUsageSetting = SubscriptionManager.USAGE_SETTING_DEFAULT;
+ int[] supportedUsageSettings;
+ final Context context = InstrumentationRegistry.getContext();
+
+ // Load the resources to provide the device capability
+ try {
+ defaultUsageSetting = context.getResources().getInteger(
+ com.android.internal.R.integer.config_default_cellular_usage_setting);
+ supportedUsageSettings = context.getResources().getIntArray(
+ com.android.internal.R.array.config_supported_cellular_usage_settings);
+ // If usage settings are not supported, return the default setting, which is UNKNOWN.
+ if (supportedUsageSettings.length < 1) {
+ isUsageSettingSupported = false;
+ fail("Usage Setting resources empty");
+ }
+ } catch (Resources.NotFoundException nfe) {
+ fail("Usage Setting resources not found");
+ isUsageSettingSupported = false;
+ }
+
+ int[] settingsToTest = new int[] {
+ SubscriptionManager.USAGE_SETTING_DEFAULT,
+ defaultUsageSetting};
+
+ for (int setting : settingsToTest) {
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putInt(CarrierConfigManager.KEY_CELLULAR_USAGE_SETTING_INT, setting);
+ overrideCarrierConfig(bundle, mSubId);
+ SubscriptionInfo info = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
+ (sm) -> sm.getActiveSubscriptionInfo(mSubId));
+ assertEquals(
+ isUsageSettingSupported ? setting :
+ SubscriptionManager.USAGE_SETTING_UNKNOWN,
+ info.getUsageSetting());
+ }
+ }
+
@Nullable
private PersistableBundle getBundleFromBackupData(byte[] data) {
try (ByteArrayInputStream bis = new ByteArrayInputStream(data)) {
@@ -1214,13 +1258,6 @@
}
}
- private void overrideCarrierConfig(PersistableBundle bundle, int subId) throws Exception {
- CarrierConfigManager carrierConfigManager = InstrumentationRegistry.getContext()
- .getSystemService(CarrierConfigManager.class);
- ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(carrierConfigManager,
- (m) -> m.overrideConfig(subId, bundle));
- }
-
private void setPreferredDataSubId(int subId) {
final LinkedBlockingQueue<Integer> resultQueue = new LinkedBlockingQueue<>(1);
Executor executor = (command)-> command.run();
@@ -1337,8 +1374,8 @@
}
private static boolean isSupported() {
- return InstrumentationRegistry.getContext().getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+ return InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION);
}
private static boolean isAutomotive() {
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyFeatureFlagsTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyFeatureFlagsTest.java
new file mode 100644
index 0000000..29ccbc4
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyFeatureFlagsTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.pm.PackageManager;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for telephony related feature flags defined in {@link android.content.pm.PackageManager}
+ */
+public final class TelephonyFeatureFlagsTest {
+
+ private PackageManager mPackageManager;
+
+ @Before
+ public void setUp() {
+ mPackageManager = getContext().getPackageManager();
+ }
+
+ @Test
+ public void testFeatureFlagsValidation() throws Exception {
+ boolean hasFeatureTelecom = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELECOM);
+ boolean hasFeatureTelephony = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY);
+ boolean hasFeatureCalling = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_CALLING);
+ boolean hasFeatureCarrierLock = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_CARRIERLOCK);
+ boolean hasFeatureCdma = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_CDMA);
+ boolean hasFeatureData = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_DATA);
+ boolean hasFeatureEuicc = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_EUICC);
+ boolean hasFeatureGsm = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_GSM);
+ boolean hasFeatureIms = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_IMS);
+ boolean hasFeatureSingleReg = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION);
+ boolean hasFeatureMbms = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_MBMS);
+ boolean hasFeatureMessaging = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_MESSAGING);
+ boolean hasFeatureRadio = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS);
+ boolean hasFeatureSubscription = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION);
+
+ if (hasFeatureCalling) {
+ assertTrue(hasFeatureTelecom && hasFeatureRadio && hasFeatureSubscription);
+ }
+
+ if (hasFeatureCarrierLock) {
+ assertTrue(hasFeatureSubscription);
+ }
+
+ if (hasFeatureCdma) {
+ assertTrue(hasFeatureRadio);
+ }
+
+ if (hasFeatureData) {
+ assertTrue(hasFeatureRadio && hasFeatureSubscription);
+ }
+
+ if (hasFeatureEuicc) {
+ assertTrue(hasFeatureSubscription);
+ }
+
+ if (hasFeatureGsm) {
+ assertTrue(hasFeatureRadio);
+ }
+
+ if (hasFeatureIms) {
+ assertTrue(hasFeatureData);
+ }
+
+ if (hasFeatureSingleReg) {
+ assertTrue(hasFeatureIms);
+ }
+
+ if (hasFeatureMbms) {
+ assertTrue(hasFeatureRadio && hasFeatureSubscription);
+ }
+
+ if (hasFeatureMessaging) {
+ assertTrue(hasFeatureRadio && hasFeatureSubscription);
+ }
+
+ if (hasFeatureRadio) {
+ assertTrue(hasFeatureTelephony);
+ }
+
+ if (hasFeatureSubscription) {
+ assertTrue(hasFeatureTelephony);
+ }
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
index 49eb546..927caf0 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -32,6 +32,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import android.Manifest.permission;
import android.annotation.NonNull;
@@ -51,6 +52,7 @@
import android.os.AsyncTask;
import android.os.Build;
import android.os.Looper;
+import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.SystemProperties;
@@ -121,6 +123,7 @@
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -334,8 +337,10 @@
@Before
public void setUp() throws Exception {
mCm = getContext().getSystemService(ConnectivityManager.class);
- mSubscriptionManager = getContext().getSystemService(SubscriptionManager.class);
mPackageManager = getContext().getPackageManager();
+ assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+
+ mSubscriptionManager = getContext().getSystemService(SubscriptionManager.class);
mCarrierConfigManager = getContext().getSystemService(CarrierConfigManager.class);
mSelfPackageName = getContext().getPackageName();
mSelfCertHash = getCertHash(mSelfPackageName);
@@ -375,7 +380,6 @@
}
private void saveAllowedNetworkTypesForAllReasons() {
- if (!hasCellular()) return;
mIsAllowedNetworkTypeChanged = false;
if (mAllowedNetworkTypesList == null) {
mAllowedNetworkTypesList = new HashMap<>();
@@ -443,15 +447,14 @@
}
}
- /** Checks whether the cellular stack should be running on this device. */
- private boolean hasCellular() {
- return mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
- && mTelephonyManager.getPhoneCount() > 0;
+ /** Checks whether the telephony feature is supported. */
+ private boolean hasFeature(String feature) {
+ return mPackageManager.hasSystemFeature(feature);
}
@Test
public void testHasCarrierPrivilegesViaCarrierConfigs() throws Exception {
- if (!hasCellular()) return;
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(mTestSub);
try {
@@ -503,10 +506,8 @@
@Test
public void testDevicePolicyApn() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
+
// These methods aren't accessible to anything except system and phone by design, so we just
// look for security exceptions here.
try {
@@ -548,12 +549,6 @@
@Test
public void testListen() throws Throwable {
- if (!InstrumentationRegistry.getContext().getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "Skipping test that requires PackageManager.FEATURE_TELEPHONY");
- return;
- }
-
if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
// TODO: temp workaround, need to adjust test to for CDMA
return;
@@ -635,11 +630,6 @@
*/
@Test
public void testTelephonyManager() {
- if (!InstrumentationRegistry.getContext().getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "Skipping test that requires PackageManager.FEATURE_TELEPHONY");
- return;
- }
assertTrue(mTelephonyManager.getNetworkType() >= TelephonyManager.NETWORK_TYPE_UNKNOWN);
assertTrue(mTelephonyManager.getPhoneType() >= TelephonyManager.PHONE_TYPE_NONE);
assertTrue(mTelephonyManager.getSimState() >= TelephonyManager.SIM_STATE_UNKNOWN);
@@ -774,10 +764,8 @@
@Test
public void testGetCallForwarding() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CALLING));
+
List<Integer> callForwardingReasons = new ArrayList<>();
callForwardingReasons.add(CallForwardingInfo.REASON_UNCONDITIONAL);
callForwardingReasons.add(CallForwardingInfo.REASON_BUSY);
@@ -840,10 +828,8 @@
@Test
public void testSetCallForwarding() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CALLING));
+
List<Integer> callForwardingReasons = new ArrayList<>();
callForwardingReasons.add(CallForwardingInfo.REASON_UNCONDITIONAL);
callForwardingReasons.add(CallForwardingInfo.REASON_BUSY);
@@ -910,6 +896,7 @@
validCallWaitingStatuses.add(TelephonyManager.CALL_WAITING_STATUS_DISABLED);
validCallWaitingStatuses.add(TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR);
validCallWaitingStatuses.add(TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED);
+ validCallWaitingStatuses.add(TelephonyManager.CALL_WAITING_STATUS_FDN_CHECK_FAILURE);
LinkedBlockingQueue<Integer> callWaitingStatusResult = new LinkedBlockingQueue<>(1);
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
@@ -921,13 +908,12 @@
@Test
public void testSetCallWaitingStatus() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CALLING));
+
Set<Integer> validCallWaitingErrors = new HashSet<Integer>();
validCallWaitingErrors.add(TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR);
validCallWaitingErrors.add(TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED);
+ validCallWaitingErrors.add(TelephonyManager.CALL_WAITING_STATUS_FDN_CHECK_FAILURE);
Executor executor = getContext().getMainExecutor();
{
LinkedBlockingQueue<Integer> callWaitingResult = new LinkedBlockingQueue<>(1);
@@ -958,11 +944,6 @@
@Test
public void testGetRadioHalVersion() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG,"skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
-
Pair<Integer, Integer> version = mTelephonyManager.getRadioHalVersion();
// The version must be valid, and the versions start with 1.0
@@ -972,10 +953,6 @@
@Test
public void testCreateForPhoneAccountHandle() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
- return;
- }
if (!mTelephonyManager.isVoiceCapable()) {
Log.d(TAG, "Skipping test that requires config_voice_capable is true");
return;
@@ -1006,10 +983,6 @@
@Test
public void testGetPhoneAccountHandle() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
- return;
- }
TelecomManager telecomManager = getContext().getSystemService(TelecomManager.class);
PhoneAccountHandle defaultAccount = telecomManager
.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
@@ -1063,11 +1036,6 @@
*/
@Test
public void testGetMaxNumberOfSimultaneouslyActiveSims() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
- return;
- }
-
int maxNum = mTelephonyManager.getMaxNumberOfSimultaneouslyActiveSims();
assertTrue(maxNum >= 1);
}
@@ -1245,30 +1213,26 @@
@Test
public void testGetNetworkCountryIso() {
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- String countryCode = mTelephonyManager.getNetworkCountryIso();
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
+ String countryCode = mTelephonyManager.getNetworkCountryIso();
+ assertTrue("Country code '" + countryCode + "' did not match "
+ + ISO_COUNTRY_CODE_PATTERN,
+ Pattern.matches(ISO_COUNTRY_CODE_PATTERN, countryCode));
+
+ for (int i = 0; i < mTelephonyManager.getPhoneCount(); i++) {
+ countryCode = mTelephonyManager.getNetworkCountryIso(i);
+
assertTrue("Country code '" + countryCode + "' did not match "
- + ISO_COUNTRY_CODE_PATTERN,
+ + ISO_COUNTRY_CODE_PATTERN + " for slot " + i,
Pattern.matches(ISO_COUNTRY_CODE_PATTERN, countryCode));
-
- for (int i = 0; i < mTelephonyManager.getPhoneCount(); i++) {
- countryCode = mTelephonyManager.getNetworkCountryIso(i);
-
- assertTrue("Country code '" + countryCode + "' did not match "
- + ISO_COUNTRY_CODE_PATTERN + " for slot " + i,
- Pattern.matches(ISO_COUNTRY_CODE_PATTERN, countryCode));
- }
- } else {
- // Non-telephony may still have the property defined if it has a SIM.
}
}
@Test
public void testSetSystemSelectionChannels() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
LinkedBlockingQueue<Boolean> queue = new LinkedBlockingQueue<>(1);
final UiAutomation uiAutomation =
InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -1310,6 +1274,8 @@
@Test
public void testGetSimCountryIso() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
String countryCode = mTelephonyManager.getSimCountryIso();
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
assertTrue("Country code '" + countryCode + "' did not match "
@@ -1322,11 +1288,6 @@
@Test
public void testResetSettings() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
-
UserManager userManager = getContext().getSystemService(UserManager.class);
boolean canChangeMobileNetworkSettings = userManager != null
@@ -1386,6 +1347,8 @@
@Test
public void testGetServiceState() throws InterruptedException {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
return;
@@ -1457,6 +1420,8 @@
@Test
public void testGetServiceStateForInactiveSub() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
return;
@@ -1479,6 +1444,8 @@
@Test
@CddTest(requirement = "7.4.1/C-4-1")
public void testIWlanServiceState() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
return;
@@ -1513,10 +1480,6 @@
@Test
public void testGetPhoneCapabilityAndVerify() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG,"skipping test that requires Telephony");
- return;
- }
boolean is5gStandalone = getContext().getResources().getBoolean(
Resources.getSystem().getIdentifier("config_telephony5gStandalone", "bool",
"android"));
@@ -1625,10 +1588,6 @@
*/
@Test
public void testGetImeiForSlot() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
-
for (int i = 0; i < mTelephonyManager.getPhoneCount(); i++) {
// The compiler error 'local variables referenced from a lambda expression must be final
// or effectively final' is reported when using i, so assign it to a final variable.
@@ -1653,9 +1612,7 @@
*/
@Test
public void testGetRadioPowerState() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
// Also verify that no exception is thrown.
assertThat(mTelephonyManager.getRadioPowerState()).isEqualTo(
@@ -1668,9 +1625,8 @@
*/
@Test
public void testSetCarrierDataEnabled() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
+
// Also verify that no exception is thrown.
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
(tm) -> tm.setCarrierDataEnabled(false));
@@ -1684,9 +1640,8 @@
*/
@Test
public void testRebootRadio() throws Throwable {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
TestThread t = new TestThread(new Runnable() {
public void run() {
Looper.prepare();
@@ -1782,9 +1737,8 @@
*/
@Test
public void testGetAidForAppType() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
(tm) -> tm.getAidForAppType(TelephonyManager.APPTYPE_SIM));
ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -1802,9 +1756,8 @@
*/
@Test
public void testGetIsimDomain() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
(tm) -> tm.getIsimDomain());
}
@@ -1816,9 +1769,8 @@
@Ignore("API moved back to @hide for Android R.")
@Test
public void testGetIsimImpu() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
TelephonyManager::getIsimImpu);
// Try without the correct permissions and ensure it fails.
@@ -1836,9 +1788,8 @@
*/
@Test
public void testNetworkRegistrationInfoRegisteredPlmn() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
// get NetworkRegistration object
ServiceState ss = mTelephonyManager.getServiceState();
assertNotNull(ss);
@@ -1870,9 +1821,8 @@
*/
@Test
public void testNetworkRegistrationInfoIsRoaming() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
// get NetworkRegistration object
NetworkRegistrationInfo nwReg = mTelephonyManager.getServiceState()
.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_CS,
@@ -1888,9 +1838,8 @@
*/
@Test
public void testNetworkRegistrationInfoGetRoamingType() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
// get NetworkRegistration object for voice
NetworkRegistrationInfo nwReg = mTelephonyManager.getServiceState()
.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_CS,
@@ -1914,9 +1863,8 @@
*/
@Test
public void testNetworkRegistationStateGetAccessNetworkTechnology() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
// get NetworkRegistration object for voice
NetworkRegistrationInfo nwReg = mTelephonyManager.getServiceState()
.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_CS,
@@ -1938,6 +1886,8 @@
*/
@Test
public void testGetMeid() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CDMA));
+
String meid = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
(tm) -> tm.getMeid());
@@ -1953,9 +1903,7 @@
*/
@Test
public void testGetMeidForSlot() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CDMA));
SubscriptionManager sm = getContext().getSystemService(SubscriptionManager.class);
List<SubscriptionInfo> subInfos = sm.getActiveSubscriptionInfoList();
@@ -1991,10 +1939,6 @@
*/
@Test
public void testSendDialerSpecialCode() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
- return;
- }
try {
mTelephonyManager.sendDialerSpecialCode("4636");
fail("Expected SecurityException. App does not have carrier privileges or is not the "
@@ -2008,9 +1952,8 @@
*/
@Test
public void testGetForbiddenPlmns() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
String[] plmns = mTelephonyManager.getForbiddenPlmns();
int phoneType = mTelephonyManager.getPhoneType();
@@ -2039,9 +1982,8 @@
*/
@Test
public void testSetForbiddenPlmns() {
- if (!supportSetFplmn()) {
- return;
- }
+ assumeTrue(supportSetFplmn());
+
String[] originalFplmns = mTelephonyManager.getForbiddenPlmns();
try {
int numFplmnsSet = ShellIdentityUtils.invokeMethodWithShellPermissions(
@@ -2063,9 +2005,8 @@
*/
@Test
public void testSetForbiddenPlmnsTruncate() {
- if (!supportSetFplmn()) {
- return;
- }
+ assumeTrue(supportSetFplmn());
+
String[] originalFplmns = mTelephonyManager.getForbiddenPlmns();
try {
List<String> targetFplmns = new ArrayList<>();
@@ -2096,9 +2037,8 @@
*/
@Test
public void testSetForbiddenPlmnsDelete() {
- if (!supportSetFplmn()) {
- return;
- }
+ assumeTrue(supportSetFplmn());
+
String[] originalFplmns = mTelephonyManager.getForbiddenPlmns();
try {
// Support test for empty SIM
@@ -2131,9 +2071,8 @@
*/
@Test
public void testSetForbiddenPlmnsVoid() {
- if (!supportSetFplmn()) {
- return;
- }
+ assumeTrue(supportSetFplmn());
+
String[] originalFplmns = mTelephonyManager.getForbiddenPlmns();
try {
ShellIdentityUtils.invokeMethodWithShellPermissions(
@@ -2149,9 +2088,7 @@
@Test
public void testGetEquivalentHomePlmns() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
List<String> plmns = mTelephonyManager.getEquivalentHomePlmns();
@@ -2175,9 +2112,8 @@
*/
@Test
public void testGetManualNetworkSelectionPlmnNonPersisted() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
if (mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_GSM) return;
try {
@@ -2199,9 +2135,8 @@
*/
@Test
public void testGetManualNetworkSelectionPlmnPersisted() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
if (mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_GSM) return;
try {
@@ -2223,6 +2158,8 @@
*/
@Test
public void testGetCardIdForDefaultEuicc() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_EUICC));
+
int cardId = mTelephonyManager.getCardIdForDefaultEuicc();
assertTrue("Card ID for default EUICC is not a valid value",
cardId == TelephonyManager.UNSUPPORTED_CARD_ID
@@ -2235,10 +2172,8 @@
*/
@Test
public void testGetUiccCardsInfoException() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
try {
// Requires READ_PRIVILEGED_PHONE_STATE or carrier privileges
List<UiccCardInfo> infos = mTelephonyManager.getUiccCardsInfo();
@@ -2252,10 +2187,8 @@
*/
@Test
public void testGetUiccCardsInfo() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
// Requires READ_PRIVILEGED_PHONE_STATE or carrier privileges
List<UiccCardInfo> infos =
ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -2282,9 +2215,7 @@
*/
@Test
public void testGetNetworkSelectionMode() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
try {
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
@@ -2304,10 +2235,8 @@
*/
@Test
public void testSetNetworkSelectionModeAutomatic() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
try {
mTelephonyManager.setNetworkSelectionModeAutomatic();
fail("Expected SecurityException. App does not have carrier privileges.");
@@ -2322,10 +2251,8 @@
*/
@Test
public void testSetNetworkSelectionModeManual() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
try {
mTelephonyManager.setNetworkSelectionModeManual(
"" /* operatorNumeric */, false /* persistSelection */);
@@ -2339,10 +2266,8 @@
*/
@Test
public void testIsManualNetworkSelectionAllowed() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
if (mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_GSM) return;
assertTrue(ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -2420,8 +2345,52 @@
assertEquals(false, cq.isRtpInactivityDetected());
assertEquals(false, cq.isIncomingSilenceDetectedAtCallSetup());
assertEquals(false, cq.isOutgoingSilenceDetectedAtCallSetup());
+ assertEquals(0, cq.getNumVoiceFrames());
+ assertEquals(0, cq.getNumNoDataFrames());
+ assertEquals(0, cq.getNumDroppedRtpPackets());
+ assertEquals(0, cq.getMinPlayoutDelayMillis());
+ assertEquals(0, cq.getMaxPlayoutDelayMillis());
+ assertEquals(0, cq.getNumRtpSidPacketsRx());
+ assertEquals(0, cq.getNumRtpDuplicatePackets());
}
+ /**
+ * Validate CallQuality Parcel
+ */
+ @Test
+ public void testCallQualityParcel() {
+ CallQuality cq = new CallQuality.Builder()
+ .setDownlinkCallQualityLevel(CallQuality.CALL_QUALITY_NOT_AVAILABLE)
+ .setUplinkCallQualityLevel(CallQuality.CALL_QUALITY_NOT_AVAILABLE)
+ .setCallDuration(20000)
+ .setNumRtpPacketsTransmitted(550)
+ .setNumRtpPacketsReceived(450)
+ .setNumRtpPacketsTransmittedLost(4)
+ .setNumRtpPacketsNotReceived(6)
+ .setAverageRelativeJitter(20)
+ .setMaxRelativeJitter(30)
+ .setAverageRoundTripTime(150)
+ .setCodecType(0)
+ .setRtpInactivityDetected(false)
+ .setIncomingSilenceDetectedAtCallSetup(false)
+ .setOutgoingSilenceDetectedAtCallSetup(false)
+ .setNumVoiceFrames(300)
+ .setNumNoDataFrames(300)
+ .setNumDroppedRtpPackets(5)
+ .setMinPlayoutDelayMillis(500)
+ .setMaxPlayoutDelayMillis(1000)
+ .setNumRtpSidPacketsRx(300)
+ .setNumRtpDuplicatePackets(0)
+ .build();
+
+ Parcel stateParcel = Parcel.obtain();
+ cq.writeToParcel(stateParcel, 0);
+ stateParcel.setDataPosition(0);
+
+ CallQuality parcelCq = CallQuality.CREATOR.createFromParcel(stateParcel);
+ assertThat(cq).isEqualTo(parcelCq);
+
+ }
// Reference: packages/services/Telephony/ecc/input/eccdata.txt
private static final Map<String, String> EMERGENCY_NUMBERS_FOR_COUNTRIES =
@@ -2444,9 +2413,8 @@
*/
@Test
public void testGetEmergencyNumberList() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CALLING));
+
Map<Integer, List<EmergencyNumber>> emergencyNumberList =
mTelephonyManager.getEmergencyNumberList();
@@ -2469,9 +2437,8 @@
*/
@Test
public void testGetEmergencyNumberListForCategories() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CALLING));
+
Map<Integer, List<EmergencyNumber>> emergencyNumberList =
mTelephonyManager.getEmergencyNumberList(
EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE);
@@ -2500,9 +2467,7 @@
*/
@Test
public void testIsEmergencyNumber() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CALLING));
for (Map.Entry<String, String> entry : EMERGENCY_NUMBERS_FOR_COUNTRIES.entrySet()) {
if (mTelephonyManager.getNetworkCountryIso().equals(entry.getKey())) {
@@ -2516,9 +2481,7 @@
*/
@Test
public void testIsPotentialEmergencyNumber() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CALLING));
String countryIso = mTelephonyManager.getNetworkCountryIso();
String potentialEmergencyAddress = "91112345";
@@ -2539,14 +2502,9 @@
*/
@Test
public void testSetGetCallComposerStatus() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CALLING));
- boolean hasImsFeature = mPackageManager.hasSystemFeature(
- PackageManager.FEATURE_TELEPHONY_IMS);
-
- if (hasImsFeature) {
+ if (hasFeature(PackageManager.FEATURE_TELEPHONY_IMS)) {
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
tm -> tm.setCallComposerStatus(TelephonyManager.CALL_COMPOSER_STATUS_OFF));
int status = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -2578,9 +2536,8 @@
*/
@Test
public void testGetRadioAccessFamily() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
long raf = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
(tm) -> tm.getSupportedRadioAccessFamily());
assertThat(raf).isNotEqualTo(TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN);
@@ -2601,6 +2558,8 @@
*/
@Test
public void testPreferredOpportunisticDataSubscription() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
+
int randomSubId = 1;
int activeSubscriptionInfoCount = ShellIdentityUtils.invokeMethodWithShellPermissions(
mSubscriptionManager, (tm) -> tm.getActiveSubscriptionInfoCount());
@@ -2727,15 +2686,14 @@
*/
@Test
public void testUpdateAvailableNetworks() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
int randomSubId = 1;
int activeSubscriptionInfoCount = ShellIdentityUtils.invokeMethodWithShellPermissions(
mSubscriptionManager, (tm) -> tm.getActiveSubscriptionInfoCount());
boolean isOpportunisticNetworkEnabled = ShellIdentityUtils.invokeMethodWithShellPermissions(
mTelephonyManager, (tm) -> tm.isOpportunisticNetworkEnabled());
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
if (!isOpportunisticNetworkEnabled) {
return;
}
@@ -2792,9 +2750,8 @@
@Test
public void testSwitchMultiSimConfig() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
try {
mTelephonyManager.switchMultiSimConfig(mTelephonyManager.getActiveModemCount());
fail("TelephonyManager#switchMultiSimConfig should require the MODIFY_PHONE_STATE"
@@ -2815,9 +2772,8 @@
@Test
public void testIccOpenLogicalChannelBySlot() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
// just verify no crash
try {
ShellIdentityUtils.invokeMethodWithShellPermissions(
@@ -2828,25 +2784,73 @@
}
@Test
- public void testIccCloseLogicalChannelBySlot() {
+ public void testIccOpenLogicalChannelBySlotAndPort() {
if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
return;
}
// just verify no crash
try {
ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.iccOpenLogicalChannelByPort(0, 0, null, 0));
+ } catch (SecurityException e) {
+ // IllegalArgumentException is okay, just not SecurityException
+ fail("iccCloseLogicalChannelByPort: SecurityException not expected");
+ }
+ }
+
+ @Test
+ public void testIccCloseLogicalChannelBySlot() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
+ // just verify no crash
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
mTelephonyManager, (tm) -> tm.iccCloseLogicalChannelBySlot(0, 0));
} catch (IllegalArgumentException e) {
// IllegalArgumentException is okay, just not SecurityException
}
}
-
@Test
- public void testIccTransmitApduLogicalChannelBySlot() {
+ public void testIccCloseLogicalChannelBySlotAndPort() {
if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
return;
}
- int slotIndex = getValidSlotIndex();
+ int slotIndex = getValidSlotIndexAndPort().getKey();
+ int portIndex = getValidSlotIndexAndPort().getValue();
+ // just verify no crash
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+ mTelephonyManager, (tm) -> tm.iccCloseLogicalChannelByPort(
+ slotIndex, portIndex, 0));
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ // IllegalArgumentException and IllegalStateException is okay, just not
+ // SecurityException
+ } catch (SecurityException e) {
+ // IllegalArgumentException is okay, just not SecurityException
+ fail("iccCloseLogicalChannelByPort: SecurityException not expected");
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+ mTelephonyManager, (tm) -> tm.iccCloseLogicalChannelByPort(slotIndex, -1, 0));
+ fail("Expected IllegalArgumentException, invalid PortIndex");
+ } catch (IllegalArgumentException e) {
+ // IllegalArgumentException is expected
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+ mTelephonyManager, (tm) -> tm.iccCloseLogicalChannelByPort(
+ slotIndex, portIndex, -1));
+ fail("Expected IllegalArgumentException, invalid channel");
+ } catch (IllegalArgumentException e) {
+ // IllegalArgumentException is expected
+ }
+ }
+
+ @Test
+ public void testIccTransmitApduLogicalChannelBySlot() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
+ int slotIndex = getValidSlotIndexAndPort().getKey();
String result = ShellIdentityUtils.invokeMethodWithShellPermissions(
mTelephonyManager, (tm) -> tm.iccTransmitApduLogicalChannelBySlot(
slotIndex,
@@ -2861,12 +2865,36 @@
}
@Test
- public void testIccTransmitApduBasicChannelBySlot() {
+ public void testIccTransmitApduLogicalChannelBySlotAndPort() {
if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
return;
}
+ int slotIndex = getValidSlotIndexAndPort().getKey();
+ int portIndex = getValidSlotIndexAndPort().getValue();
+ try {
+ String result = ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.iccTransmitApduLogicalChannelByPort(
+ slotIndex,
+ portIndex /* portIndex */,
+ 0 /* channel */,
+ 0 /* cla */,
+ 0 /* instruction */,
+ 0 /* p1 */,
+ 0 /* p2 */,
+ 0 /* p3 */,
+ null /* data */));
+ assertTrue(TextUtils.isEmpty(result));
+ } catch (SecurityException e) {
+ // IllegalArgumentException is okay, just not SecurityException
+ fail("iccTransmitApduLogicalChannelByPort: SecurityException not expected");
+ }
+ }
+ @Test
+ public void testIccTransmitApduBasicChannelBySlot() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
// just verify no crash
- int slotIndex = getValidSlotIndex();
+ int slotIndex = getValidSlotIndexAndPort().getKey();
try {
ShellIdentityUtils.invokeMethodWithShellPermissions(
mTelephonyManager, (tm) -> tm.iccTransmitApduBasicChannelBySlot(
@@ -2883,10 +2911,34 @@
}
@Test
- public void testIsIccLockEnabled() {
+ public void testIccTransmitApduBasicChannelBySlotAndPort() {
if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
return;
}
+ // just verify no crash
+ int slotIndex = getValidSlotIndexAndPort().getKey();
+ int portIndex = getValidSlotIndexAndPort().getValue();
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.iccTransmitApduBasicChannelByPort(
+ slotIndex,
+ portIndex /*portIndex */,
+ 0 /* cla */,
+ 0 /* instruction */,
+ 0 /* p1 */,
+ 0 /* p2 */,
+ 0 /* p3 */,
+ null /* data */));
+ } catch (SecurityException e) {
+ // IllegalArgumentException is okay, just not SecurityException
+ fail("iccTransmitApduBasicChannelByPort: SecurityException not expected");
+ }
+ }
+
+ @Test
+ public void testIsIccLockEnabled() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
// verify SecurityException
try {
mTelephonyManager.isIccLockEnabled();
@@ -2906,9 +2958,8 @@
@Test
public void testIsDataEnabledForApn() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
+
// verify SecurityException
try {
mTelephonyManager.isDataEnabledForApn(ApnSetting.TYPE_MMS);
@@ -2928,9 +2979,8 @@
@Test
public void testIsTetheringApnRequired() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
+
// verify SecurityException
try {
mTelephonyManager.isTetheringApnRequired();
@@ -2951,9 +3001,8 @@
@Test
public void testGetCarrierInfoForImsiEncryption() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
// test without permission: verify SecurityException
try {
mTelephonyManager.getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_EPDG);
@@ -3044,9 +3093,8 @@
@Test
public void testResetCarrierKeysForImsiEncryption() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
// test without permission: verify SecurityException
try {
mTelephonyManager.resetCarrierKeysForImsiEncryption();
@@ -3066,9 +3114,8 @@
@Test
public void testIsInEmergencySmsMode() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING));
+
// test without permission: verify SecurityException
try {
mTelephonyManager.isInEmergencySmsMode();
@@ -3088,9 +3135,7 @@
@Test
public void testGetSubscriptionId() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
TelephonyManager tm = mTelephonyManager.createForSubscriptionId(1);
int subId = tm.getSubscriptionId();
@@ -3099,9 +3144,7 @@
@Test
public void testSetAllowedNetworkTypes() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
// test without permission: verify SecurityException
long allowedNetworkTypes = TelephonyManager.NETWORK_TYPE_BITMASK_NR;
@@ -3131,9 +3174,7 @@
@Test
public void testDisAllowedNetworkTypes() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
long allowedNetworkTypes = -1 & (~TelephonyManager.NETWORK_TYPE_BITMASK_NR);
long networkTypeBitmask = TelephonyManager.NETWORK_TYPE_BITMASK_NR
@@ -3174,9 +3215,7 @@
@Test
public void testSetAllowedNetworkTypesForReason() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
// test without permission: verify SecurityException
long allowedNetworkTypes = TelephonyManager.NETWORK_TYPE_BITMASK_NR;
@@ -3211,9 +3250,7 @@
@Test
public void testSetAllowedNetworkTypesForReason_moreReason() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
// test without permission: verify SecurityException
long allowedNetworkTypes1 = TelephonyManager.NETWORK_TYPE_BITMASK_NR
@@ -3284,9 +3321,7 @@
@Test
public void testIsApplicationOnUicc() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
// Expect a security exception without permission.
try {
@@ -3310,10 +3345,6 @@
@Test
public void testRequestModemActivityInfo() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
-
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity("android.permission.MODIFY_PHONE_STATE");
try {
@@ -3353,10 +3384,6 @@
@Test
public void testGetSupportedModemCount() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
-
int supportedModemCount = mTelephonyManager.getSupportedModemCount();
int activeModemCount = mTelephonyManager.getActiveModemCount();
assertTrue(activeModemCount >= 0);
@@ -3396,10 +3423,6 @@
@Test
public void testIsModemEnabledForSlot() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
-
int activeModemCount = mTelephonyManager.getActiveModemCount();
for (int i = 0; i < activeModemCount; i++) {
// Call isModemEnabledForSlot for each slot and verify no crash.
@@ -3409,12 +3432,8 @@
@Test
public void testOpportunisticNetworkState() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
+ && !mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH));
boolean isEnabled = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
tm -> tm.isOpportunisticNetworkEnabled());
@@ -3432,9 +3451,8 @@
@Test
public void testGetSimApplicationState() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
int simApplicationState = mTelephonyManager.getSimApplicationState();
assertTrue(Arrays.asList(TelephonyManager.SIM_STATE_UNKNOWN,
TelephonyManager.SIM_STATE_PIN_REQUIRED,
@@ -3460,9 +3478,8 @@
@Test
public void testGetSimCardState() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
int simCardState = mTelephonyManager.getSimCardState();
assertTrue(Arrays.asList(TelephonyManager.SIM_STATE_UNKNOWN,
TelephonyManager.SIM_STATE_ABSENT,
@@ -3470,6 +3487,27 @@
TelephonyManager.SIM_STATE_CARD_RESTRICTED,
TelephonyManager.SIM_STATE_PRESENT).contains(simCardState));
}
+ @Test
+ public void getSimCardStateTest() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity("android.permission.READ_PRIVILEGED_PHONE_STATE");
+ List<UiccCardInfo> cardsInfo = mTelephonyManager.getUiccCardsInfo();
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ for (UiccCardInfo cardInfo : cardsInfo) {
+ for (UiccPortInfo portInfo : cardInfo.getPorts()) {
+ int simCardState = mTelephonyManager.getSimCardState(cardInfo
+ .getPhysicalSlotIndex(), portInfo.getPortIndex());
+ assertTrue(Arrays.asList(TelephonyManager.SIM_STATE_UNKNOWN,
+ TelephonyManager.SIM_STATE_ABSENT,
+ TelephonyManager.SIM_STATE_CARD_IO_ERROR,
+ TelephonyManager.SIM_STATE_CARD_RESTRICTED,
+ TelephonyManager.SIM_STATE_PRESENT).contains(simCardState));
+ }
+ }
+ }
private boolean isDataEnabled() {
return ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -3478,10 +3516,11 @@
@Test
public void testThermalDataEnable() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
+ // Perform this test on default data subscription.
+ mTelephonyManager = getContext().getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId());
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
mTelephonyManager,
(tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_THERMAL,
@@ -3513,10 +3552,11 @@
@Test
public void testPolicyDataEnable() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
+ // Perform this test on default data subscription.
+ mTelephonyManager = getContext().getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId());
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
mTelephonyManager,
(tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_POLICY,
@@ -3548,10 +3588,11 @@
@Test
public void testCarrierDataEnable() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
+ // Perform this test on default data subscription.
+ mTelephonyManager = getContext().getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId());
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
mTelephonyManager,
(tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_CARRIER,
@@ -3584,9 +3625,7 @@
@Test
public void testUserDataEnable() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
mTelephonyManager,
@@ -3620,9 +3659,7 @@
@Test
public void testDataDuringVoiceCallPolicy() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
ShellIdentityUtils.ShellPermissionMethodHelper<Boolean, TelephonyManager> getPolicyHelper =
(tm) -> tm.isMobileDataPolicyEnabled(
@@ -3652,9 +3689,7 @@
@Test
public void testAlwaysAllowMmsDataPolicy() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
ShellIdentityUtils.ShellPermissionMethodHelper<Boolean, TelephonyManager> getPolicyHelper =
(tm) -> tm.isMobileDataPolicyEnabled(
@@ -3684,6 +3719,8 @@
@Test
public void testGetCdmaEnhancedRoamingIndicatorDisplayNumber() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CDMA));
+
int index = mTelephonyManager.getCdmaEnhancedRoamingIndicatorDisplayNumber();
int phoneType = mTelephonyManager.getPhoneType();
if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
@@ -3720,9 +3757,7 @@
@Test
public void testNrDualConnectivityEnable() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
if (!ShellIdentityUtils.invokeMethodWithShellPermissions(
mTelephonyManager, (tm) -> tm.isRadioInterfaceCapabilitySupported(
@@ -3766,10 +3801,8 @@
@Test
public void testCdmaRoamingMode() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
- || mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
+ && mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA);
// Save state
int cdmaRoamingMode = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -3793,10 +3826,8 @@
@Test
public void testCdmaSubscriptionMode() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
- || mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
+ && mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA);
// Save state
int cdmaSubscriptionMode = ShellIdentityUtils.invokeMethodWithShellPermissions(
@@ -3820,9 +3851,8 @@
@Test
public void testPinResult() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
final String empty_pin = ""; // For getting current remaining pin attempt.
final String pin = "fake_pin";
final String puk = "fake_puk";
@@ -3870,10 +3900,7 @@
@Test
public void testSetSignalStrengthUpdateRequest_nullRequest() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
// Verify NPE throws if set request with null object
try {
@@ -3885,10 +3912,7 @@
@Test
public void testSetSignalStrengthUpdateRequest_noPermission() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
final SignalStrengthUpdateRequest normalRequest =
new SignalStrengthUpdateRequest.Builder()
@@ -3914,10 +3938,7 @@
@Test
public void testSetSignalStrengthUpdateRequest_systemThresholdReportingRequestedWhileIdle() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
// Verify system privileged app with permission LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH can
// set systemThresholdReportingRequestedWhileIdle to true with empty thresholdInfos
@@ -3932,10 +3953,7 @@
@Test
public void testSetSignalStrengthUpdateRequest_hysteresisDbSet() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
// Verify SE throws for app when set hysteresisDb in the SignalThresholdInfo
SignalStrengthUpdateRequest requestWithHysteresisDbSet =
@@ -3962,10 +3980,7 @@
@Test
public void testSetSignalStrengthUpdateRequest_hysteresisMsSet() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
// Verify SE throws for app when set hysteresisMs in the SignalThresholdInfo
SignalStrengthUpdateRequest requestWithHysteresisMsSet =
@@ -3992,10 +4007,7 @@
@Test
public void testSetSignalStrengthUpdateRequest_isEnabledSet() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
// Verify SE throws for app when set isEnabled in the SignalThresholdInfo
SignalStrengthUpdateRequest requestWithThresholdIsEnabledSet =
@@ -4022,10 +4034,7 @@
@Test
public void testSetSignalStrengthUpdateRequest_tooShortThresholds() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
// verify SE throws if app set too short thresholds
SignalStrengthUpdateRequest requestWithTooShortThresholds =
@@ -4050,10 +4059,7 @@
@Test
public void testSetSignalStrengthUpdateRequest_tooLongThresholds() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
// verify SE throws if app set too long thresholds
SignalStrengthUpdateRequest requestWithTooLongThresholds =
@@ -4079,10 +4085,7 @@
@Test
public void testSetSignalStrengthUpdateRequest_duplicatedRequest() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
final SignalStrengthUpdateRequest normalRequest =
new SignalStrengthUpdateRequest.Builder()
@@ -4114,10 +4117,7 @@
@Test
public void testClearSignalStrengthUpdateRequest_nullRequest() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
// Verify NPE should throw if clear request with null object
try {
@@ -4129,10 +4129,7 @@
@Test
public void testClearSignalStrengthUpdateRequest_noPermission() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
final SignalStrengthUpdateRequest normalRequest =
new SignalStrengthUpdateRequest.Builder()
@@ -4158,10 +4155,7 @@
@Test
public void testClearSignalStrengthUpdateRequest_clearWithNoSet() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
SignalStrengthUpdateRequest requestNeverSetBefore = new SignalStrengthUpdateRequest
.Builder()
@@ -4180,9 +4174,7 @@
@Test
public void testSendThermalMitigationRequest() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
StringBuilder cmdBuilder = new StringBuilder();
cmdBuilder.append(THERMAL_MITIGATION_COMMAND_BASE).append(ALLOW_PACKAGE_SUBCOMMAND)
@@ -4300,7 +4292,7 @@
@Test
public void testIsRadioInterfaceCapabilitySupported() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) return;
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
assertFalse(mTelephonyManager.isRadioInterfaceCapabilitySupported("empty"));
assertFalse(mTelephonyManager.isRadioInterfaceCapabilitySupported(null));
@@ -4309,7 +4301,7 @@
@Test
public void testGetAllCellInfo() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) return;
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
// For IRadio <1.5, just verify that calling the method doesn't throw an error.
if (mRadioVersion < RADIO_HAL_VERSION_1_5) {
@@ -4504,9 +4496,10 @@
return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == 'N';
}
- private int getValidSlotIndex() {
+ private Map.Entry<Integer, Integer> getValidSlotIndexAndPort() {
return ShellIdentityUtils.invokeMethodWithShellPermissions(
mTelephonyManager, (tm) -> {
+
List<UiccCardInfo> cardInfos = mTelephonyManager.getUiccCardsInfo();
Set<String> presentCards = Arrays.stream(mTelephonyManager.getUiccSlotsInfo())
.filter(Objects::nonNull)
@@ -4518,11 +4511,15 @@
.map(s -> s.endsWith("F") ? s.substring(0, s.length() - 1) : s)
.collect(Collectors.toSet());
int slotIndex = -1;
+ int portIndex = -1;
for (UiccCardInfo cardInfo : cardInfos) {
for (UiccPortInfo portInfo : cardInfo.getPorts()) {
if (presentCards.contains(portInfo.getIccId())
|| presentCards.contains(cardInfo.getEid())) {
slotIndex = cardInfo.getPhysicalSlotIndex();
+ portIndex = portInfo.getPortIndex();
+ Log.d(TAG, "SlotIndex : " + slotIndex + " and portIndex :"
+ + portIndex);
break;
}
}
@@ -4531,7 +4528,7 @@
fail("Test must be run with SIM card inserted, presentCards = "
+ presentCards + "cardinfos = " + cardInfos);
}
- return slotIndex;
+ return Map.entry(slotIndex, portIndex);
});
}
@@ -4549,7 +4546,7 @@
* @return whether to proceed the test
*/
private boolean supportSetFplmn() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ if (!hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)) {
return false;
}
return mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM;
@@ -4596,12 +4593,6 @@
@Test
public void testRegisterTelephonyCallbackWithNonLooper() throws Throwable {
- if (!InstrumentationRegistry.getContext().getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "Skipping test that requires PackageManager.FEATURE_TELEPHONY");
- return;
- }
-
mMockSignalStrengthsTelephonyCallback = new MockSignalStrengthsTelephonyCallback();
// Test register, generates an mOnSignalStrengthsChanged event
@@ -4642,12 +4633,6 @@
@Test
public void testRegisterTelephonyCallback() throws Throwable {
- if (!InstrumentationRegistry.getContext().getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "Skipping test that requires PackageManager.FEATURE_TELEPHONY");
- return;
- }
-
if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
// TODO: temp workaround, need to adjust test to for CDMA
return;
@@ -4738,9 +4723,8 @@
*/
@Test
public void testGetNetworkSlicingConfiguration() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
CompletableFuture<NetworkSlicingConfig> resultFuture = new CompletableFuture<>();
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
(tm) -> tm.getNetworkSlicingConfiguration(mSimpleExecutor, resultFuture::complete));
@@ -4748,6 +4732,8 @@
@Test
public void testCheckCarrierPrivilegesForPackageEnforcesReadPrivilege() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
try {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity("android.permission.READ_PRIVILEGED_PHONE_STATE");
@@ -4763,7 +4749,8 @@
@Test
public void testCheckCarrierPrivilegesForPackageThrowsExceptionWithoutReadPrivilege() {
- if (!hasCellular()) return;
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
try {
mTelephonyManager.checkCarrierPrivilegesForPackage(mSelfPackageName);
fail("TelephonyManager#checkCarrierPrivilegesForPackage must be protected "
@@ -4775,6 +4762,8 @@
@Test
public void testCheckCarrierPrivilegesForPackageAnyPhone() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
try {
mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(mSelfPackageName);
} catch (SecurityException e) {
@@ -4785,6 +4774,8 @@
@Test
public void testGetCarrierPackageNamesForIntentAndPhoneEnforcesReadPrivilege() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
try {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity("android.permission.READ_PRIVILEGED_PHONE_STATE");
@@ -4802,7 +4793,8 @@
@Test
public void testGetCarrierPackageNamesForIntentAndPhoneThrowsExceptionWithoutReadPrivilege() {
- if (!hasCellular()) return;
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
try {
Intent intent = new Intent();
int phoneId = 1;
@@ -4819,6 +4811,8 @@
@Test
public void testGetPackagesWithCarrierPrivilegesEnforcesReadPrivilege() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
try {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity("android.permission.READ_PRIVILEGED_PHONE_STATE");
@@ -4834,7 +4828,8 @@
@Test
public void testGetPackagesWithCarrierPrivilegesThrowsExceptionWithoutReadPrivilege() {
- if (!hasCellular()) return;
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
try {
mTelephonyManager.getPackagesWithCarrierPrivileges();
fail("TelephonyManager#getPackagesWithCarrierPrivileges must be protected "
@@ -4846,7 +4841,8 @@
@Test
public void testSimSlotMapping() {
- if (!hasCellular()) return;
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity("android.permission.MODIFY_PHONE_STATE");
// passing slotMapping combination
@@ -4908,6 +4904,8 @@
@Test
public void getUiccSlotInfoTest() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
UiccSlotInfo[] slotInfos = mTelephonyManager.getUiccSlotsInfo();
if (slotInfos == null) {
@@ -4929,5 +4927,47 @@
}
}
}
+
+ @Test
+ public void getSimSlotMappingTestReadPermission() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
+ try {
+ Collection<UiccSlotMapping> simSlotMapping = mTelephonyManager.getSimSlotMapping();
+ fail("Expected SecurityException, no READ_PRIVILEGED_PHONE_STATE permission");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+ @Test
+ public void getSimSlotMappingTest() {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity("android.permission.READ_PRIVILEGED_PHONE_STATE");
+ try {
+ Collection<UiccSlotMapping> simSlotMapping = mTelephonyManager.getSimSlotMapping();
+ assertTrue(isSlotMappingValid(simSlotMapping));
+ } catch (IllegalArgumentException e) {
+ fail("IllegalArgumentException, Duplicate UiccSlotMapping data found");
+ } finally {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+ }
+ private static boolean isSlotMappingValid(@NonNull Collection<UiccSlotMapping> slotMapping) {
+ // Grouping the collection by logicalSlotIndex, finding different entries mapping to the
+ // same logical slot
+ Map<Integer, List<UiccSlotMapping>> slotMappingInfo = slotMapping.stream().collect(
+ Collectors.groupingBy(UiccSlotMapping::getLogicalSlotIndex));
+ for (Map.Entry<Integer, List<UiccSlotMapping>> entry : slotMappingInfo.entrySet()) {
+ List<UiccSlotMapping> logicalSlotMap = entry.getValue();
+ if (logicalSlotMap.size() > 1) {
+ // duplicate logicalSlotIndex found
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java
index 8a970e7..613ead9 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java
@@ -15,7 +15,11 @@
*/
package android.telephony.cts;
+import static com.android.internal.telephony.RILConstants.INTERNAL_ERR;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_RADIO_POWER;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -90,8 +94,7 @@
sMockModem.resetState();
sMockModem.unsolSimSlotsStatusChanged();
- assertTrue(
- sMockModem.waitForLatchCountdown(MockModemService.LATCH_MOCK_MODEM_SIM_READY));
+ assertTrue(sMockModem.waitForLatchCountdown(MockModemService.LATCH_MOCK_MODEM_SIM_READY));
TimeUnit.SECONDS.sleep(1);
simCardState = mTelephonyManager.getSimCardState();
@@ -100,6 +103,50 @@
}
@Test
+ public void testRadioPowerToggle() throws Throwable {
+ Log.d(TAG, "TelephonyManagerTestOnMockModem#testRadioPowerToggle");
+
+ int radioState = mTelephonyManager.getRadioPowerState();
+ Log.d(TAG, "Radio state: " + radioState);
+
+ // Toggle radio power
+ try {
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+ mTelephonyManager,
+ (tm) -> tm.toggleRadioOnOff(),
+ SecurityException.class,
+ "android.permission.MODIFY_PHONE_STATE");
+ } catch (SecurityException e) {
+ Log.d(TAG, "TelephonyManager#toggleRadioOnOff should require " + e);
+ }
+
+ // Wait the radio state update in Framework
+ TimeUnit.SECONDS.sleep(1);
+ int toggleRadioState =
+ radioState == TelephonyManager.RADIO_POWER_ON
+ ? TelephonyManager.RADIO_POWER_OFF
+ : TelephonyManager.RADIO_POWER_ON;
+ assertEquals(mTelephonyManager.getRadioPowerState(), toggleRadioState);
+
+ // Toggle radio power again back to original radio state
+ try {
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+ mTelephonyManager,
+ (tm) -> tm.toggleRadioOnOff(),
+ SecurityException.class,
+ "android.permission.MODIFY_PHONE_STATE");
+ } catch (SecurityException e) {
+ Log.d(TAG, "TelephonyManager#toggleRadioOnOff should require " + e);
+ }
+
+ // Wait the radio state update in Framework
+ TimeUnit.SECONDS.sleep(1);
+ assertEquals(mTelephonyManager.getRadioPowerState(), radioState);
+
+ Log.d(TAG, "Test Done ");
+ }
+
+ @Test
public void testRadioPower() throws Throwable {
Log.d(TAG, "TelephonyManagerTestOnMockModem#testRadioPower");
@@ -184,4 +231,57 @@
Log.d(TAG, "Test Done ");
}
+
+ @Test
+ public void testRadioPowerWithFailureResults() throws Throwable {
+ Log.d(TAG, "TelephonyManagerTestOnMockModem#testRadioPowerWithFailureResults");
+
+ int radioState = mTelephonyManager.getRadioPowerState();
+ Log.d(TAG, "Radio state: " + radioState);
+
+ int toggleRadioState =
+ radioState == TelephonyManager.RADIO_POWER_ON
+ ? TelephonyManager.RADIO_POWER_OFF
+ : TelephonyManager.RADIO_POWER_ON;
+
+ // Force the returned response of RIL_REQUEST_RADIO_POWER as INTERNAL_ERR
+ sMockModem.forceErrorResponse(RIL_REQUEST_RADIO_POWER, INTERNAL_ERR);
+
+ boolean result = false;
+ try {
+ boolean state = (toggleRadioState == TelephonyManager.RADIO_POWER_ON) ? true : false;
+ result =
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+ mTelephonyManager,
+ (tm) -> tm.setRadioPower(state),
+ SecurityException.class,
+ "android.permission.MODIFY_PHONE_STATE");
+ } catch (SecurityException e) {
+ Log.d(TAG, "TelephonyManager#setRadioPower should require " + e);
+ }
+
+ TimeUnit.SECONDS.sleep(1);
+ assertTrue(result);
+ assertNotEquals(mTelephonyManager.getRadioPowerState(), toggleRadioState);
+
+ // Reset the modified error response of RIL_REQUEST_RADIO_POWER to the original behavior
+ // and -1 means to disable the modifed mechanism in mock modem
+ sMockModem.forceErrorResponse(RIL_REQUEST_RADIO_POWER, -1);
+
+ // Recovery the power state back to original radio state
+ try {
+ boolean state = (radioState == TelephonyManager.RADIO_POWER_ON) ? true : false;
+ result =
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+ mTelephonyManager,
+ (tm) -> tm.setRadioPower(state),
+ SecurityException.class,
+ "android.permission.MODIFY_PHONE_STATE");
+ } catch (SecurityException e) {
+ Log.d(TAG, "TelephonyManager#setRadioPower should require " + e);
+ }
+ TimeUnit.SECONDS.sleep(1);
+ assertTrue(result);
+ assertEquals(mTelephonyManager.getRadioPowerState(), radioState);
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/UiccSlotMappingTest.java b/tests/tests/telephony/current/src/android/telephony/cts/UiccSlotMappingTest.java
new file mode 100644
index 0000000..95b7f8b
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/UiccSlotMappingTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.telephony.UiccSlotMapping;
+
+import org.junit.Test;
+
+public class UiccSlotMappingTest {
+ private static final int PORT_INDEX = 0;
+ private static final int PHYSICAL_SLOT_INDEX = 0;
+ private static final int LOGICAL_SLOT_INDEX = 0;
+
+ @Test
+ public void testConstructorAndGetters() {
+ UiccSlotMapping uiccSlotMapping = new UiccSlotMapping(
+ PORT_INDEX, PHYSICAL_SLOT_INDEX, LOGICAL_SLOT_INDEX);
+
+ assertThat(uiccSlotMapping.getPortIndex()).isEqualTo(PORT_INDEX);
+ assertThat(uiccSlotMapping.getPhysicalSlotIndex()).isEqualTo(PHYSICAL_SLOT_INDEX);
+ assertThat(uiccSlotMapping.getLogicalSlotIndex()).isEqualTo(LOGICAL_SLOT_INDEX);
+ }
+
+ @Test
+ public void testEquals() {
+ UiccSlotMapping uiccSlotMappingObject = new UiccSlotMapping(
+ PORT_INDEX, PHYSICAL_SLOT_INDEX, LOGICAL_SLOT_INDEX);
+ UiccSlotMapping uiccSlotMappingEqualObject = new UiccSlotMapping(
+ PORT_INDEX, PHYSICAL_SLOT_INDEX, LOGICAL_SLOT_INDEX);
+
+ assertThat(uiccSlotMappingObject).isEqualTo(uiccSlotMappingEqualObject);
+ }
+
+ @Test
+ public void testNotEqual() {
+ UiccSlotMapping uiccSlotMappingObject = new UiccSlotMapping(
+ PORT_INDEX, PHYSICAL_SLOT_INDEX, LOGICAL_SLOT_INDEX);
+ UiccSlotMapping uiccSlotMappingNotEqualObject = new UiccSlotMapping(
+ /* portIndex= */ 0, /* phycalSlotIndex= */1, /* logicalSlotIndex= */ 1);
+
+ assertThat(uiccSlotMappingObject).isNotEqualTo(uiccSlotMappingNotEqualObject);
+ }
+
+ @Test
+ public void testParcel() {
+ UiccSlotMapping uiccSlotMapping = new UiccSlotMapping(
+ PORT_INDEX, PHYSICAL_SLOT_INDEX, LOGICAL_SLOT_INDEX);
+
+ Parcel parcel = Parcel.obtain();
+ uiccSlotMapping.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ UiccSlotMapping uiccSlotMappingParcel = UiccSlotMapping.CREATOR.createFromParcel(parcel);
+
+ assertThat(uiccSlotMapping).isEqualTo(uiccSlotMappingParcel);
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/VisualVoicemailServiceTest.java b/tests/tests/telephony/current/src/android/telephony/cts/VisualVoicemailServiceTest.java
index 31ba58f..6c19696 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/VisualVoicemailServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/VisualVoicemailServiceTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import android.app.Instrumentation;
import android.content.BroadcastReceiver;
@@ -40,7 +41,6 @@
import android.os.ParcelFileDescriptor;
import android.provider.Telephony.Sms;
import android.provider.Telephony.Sms.Intents;
-import androidx.annotation.Nullable;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
@@ -52,6 +52,12 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.Nullable;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
@@ -66,10 +72,6 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
public class VisualVoicemailServiceTest {
private static final String TAG = "VvmServiceTest";
@@ -96,20 +98,20 @@
@Before
public void setUp() throws Exception {
mContext = getInstrumentation().getContext();
- if (hasTelephony(mContext)) {
- mPreviousDefaultDialer = getDefaultDialer(getInstrumentation());
- setDefaultDialer(getInstrumentation(), PACKAGE);
+ assumeTrue(hasFeatureSupported(mContext));
- TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
- mPhoneAccountHandle = telecomManager
- .getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
- assertNotNull(mPhoneAccountHandle);
- mPhoneNumber = telecomManager.getLine1Number(mPhoneAccountHandle);
- assertNotNull(mPhoneNumber, "Tests require a line1 number for the active SIM.");
+ mPreviousDefaultDialer = getDefaultDialer(getInstrumentation());
+ setDefaultDialer(getInstrumentation(), PACKAGE);
- mTelephonyManager = mContext.getSystemService(TelephonyManager.class)
- .createForPhoneAccountHandle(mPhoneAccountHandle);
- }
+ TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
+ mPhoneAccountHandle = telecomManager
+ .getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
+ assertNotNull(mPhoneAccountHandle);
+ mPhoneNumber = telecomManager.getLine1Number(mPhoneAccountHandle);
+ assertNotNull(mPhoneNumber, "Tests require a line1 number for the active SIM.");
+
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class)
+ .createForPhoneAccountHandle(mPhoneAccountHandle);
PackageManager packageManager = mContext.getPackageManager();
packageManager.setComponentEnabledSetting(
@@ -122,7 +124,7 @@
@After
public void tearDown() throws Exception {
- if (hasTelephony(mContext)) {
+ if (hasFeatureSupported(mContext)) {
if (!TextUtils.isEmpty(mPreviousDefaultDialer)) {
setDefaultDialer(getInstrumentation(), mPreviousDefaultDialer);
}
@@ -135,11 +137,6 @@
@Test
public void testPermissionlessService_ignored() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
-
PackageManager packageManager = mContext.getPackageManager();
packageManager.setComponentEnabledSetting(
new ComponentName(mContext, MockVisualVoicemailService.class),
@@ -184,10 +181,6 @@
@Test
public void testFilter() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
VisualVoicemailSms result = getSmsFromText("//CTSVVM",
"//CTSVVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1");
@@ -204,10 +197,6 @@
@Test
public void testFilter_data() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
if (!hasDataSms()) {
Log.d(TAG, "skipping test that requires data SMS feature");
return;
@@ -233,10 +222,6 @@
@Test
public void testFilter_TrailingSemiColon() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
VisualVoicemailSms result = getSmsFromText("//CTSVVM",
"//CTSVVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1;");
@@ -253,10 +238,6 @@
@Test
public void testFilter_EmptyPrefix() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
VisualVoicemailSms result = getSmsFromText("//CTSVVM",
"//CTSVVM::st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1");
@@ -273,10 +254,6 @@
@Test
public void testFilter_EmptyField() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
VisualVoicemailSms result = getSmsFromText("//CTSVVM",
"//CTSVVM:STATUS:");
assertTrue(result.getFields().isEmpty());
@@ -284,90 +261,54 @@
@Test
public void testFilterFail_NotVvm() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
assertVisualVoicemailSmsNotReceived("//CTSVVM",
"helloworld");
}
@Test
public void testFilterFail_PrefixMismatch() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
assertVisualVoicemailSmsNotReceived("//CTSVVM",
"//FOOVVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1");
}
@Test
public void testFilterFail_MissingFirstColon() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
assertVisualVoicemailSmsNotReceived("//CTSVVM",
"//CTSVVMSTATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1");
}
@Test
public void testFilterFail_MissingSecondColon() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
assertVisualVoicemailSmsNotReceived("//CTSVVM",
"//CTSVVM:STATUSst=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1");
}
@Test
public void testFilterFail_MessageEndAfterClientPrefix() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
assertVisualVoicemailSmsNotReceived("//CTSVVM",
"//CTSVVM:");
}
@Test
public void testFilterFail_MessageEndAfterPrefix() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
assertVisualVoicemailSmsNotReceived("//CTSVVM",
"//CTSVVM:STATUS");
}
@Test
public void testFilterFail_InvalidKeyValuePair() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
assertVisualVoicemailSmsNotReceived("//CTSVVM",
"//CTSVVM:STATUS:key");
}
@Test
public void testFilterFail_InvalidMissingKey() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
assertVisualVoicemailSmsNotReceived("//CTSVVM",
"//CTSVVM:STATUS:=value");
}
@Test
public void testFilter_MissingValue() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
VisualVoicemailSms result = getSmsFromText("//CTSVVM",
"//CTSVVM:STATUS:key=");
assertEquals("STATUS", result.getPrefix());
@@ -376,10 +317,6 @@
@Test
public void testFilter_originatingNumber_match_filtered() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
VisualVoicemailSmsFilterSettings settings = new VisualVoicemailSmsFilterSettings.Builder()
.setClientPrefix("//CTSVVM")
.setOriginatingNumbers(Arrays.asList(mPhoneNumber))
@@ -390,10 +327,6 @@
@Test
public void testFilter_originatingNumber_mismatch_notFiltered() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
VisualVoicemailSmsFilterSettings settings = new VisualVoicemailSmsFilterSettings.Builder()
.setClientPrefix("//CTSVVM")
.setOriginatingNumbers(Arrays.asList("1"))
@@ -404,10 +337,6 @@
@Test
public void testFilter_port_match() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
if (!hasDataSms()) {
Log.d(TAG, "skipping test that requires data SMS feature");
return;
@@ -423,10 +352,6 @@
@Test
public void testFilter_port_mismatch() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
if (!hasDataSms()) {
Log.d(TAG, "skipping test that requires data SMS feature");
return;
@@ -442,10 +367,6 @@
@Test
public void testFilter_port_anydata() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
if (!hasDataSms()) {
Log.d(TAG, "skipping test that requires data SMS feature");
return;
@@ -464,10 +385,6 @@
*/
@Test
public void testFilter_port_anydata_notData() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
if (!hasDataSms()) {
Log.d(TAG, "skipping test that requires data SMS feature");
return;
@@ -483,10 +400,6 @@
@Test
public void testGetVisualVoicemailPackageName_isSelf() {
- if (!hasTelephony(mContext)) {
- Log.d(TAG, "skipping test that requires telephony feature");
- return;
- }
assertEquals(PACKAGE, mTelephonyManager.getVisualVoicemailPackageName());
}
@@ -704,10 +617,9 @@
}
}
- private static boolean hasTelephony(Context context) {
- final PackageManager packageManager = context.getPackageManager();
- return packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) &&
- packageManager.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE);
+ private static boolean hasFeatureSupported(Context context) {
+ return context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_CALLING);
}
private boolean hasDataSms() {
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
index eae33de..c871586 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
@@ -213,6 +213,36 @@
}
@Test
+ public void testSwitchToSubscriptionWithCallback() {
+ // test disabled state only for now
+ if (mEuiccManager.isEnabled()) {
+ return;
+ }
+
+ // set up CountDownLatch and receiver
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ mCallbackReceiver = new CallbackReceiver(countDownLatch);
+ getContext()
+ .registerReceiver(
+ mCallbackReceiver, new IntentFilter(ACTION_SWITCH_TO_SUBSCRIPTION));
+
+ // call deleteSubscription()
+ PendingIntent callbackIntent = createCallbackIntent(ACTION_SWITCH_TO_SUBSCRIPTION);
+ mEuiccManager.switchToSubscription(4, TelephonyManager.DEFAULT_PORT_INDEX, callbackIntent);
+
+ // wait for callback
+ try {
+ countDownLatch.await(CALLBACK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ }
+
+ // verify correct result code is received
+ assertEquals(
+ EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, mCallbackReceiver.getResultCode());
+ }
+
+ @Test
public void testEraseSubscriptions() {
// test disabled state only for now
if (mEuiccManager.isEnabled()) {
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccServiceTest.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccServiceTest.java
index c506e8e6..9416ba3 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccServiceTest.java
@@ -69,6 +69,7 @@
private static final int CALLBACK_TIMEOUT_MILLIS = 2000 /* 2 sec */;
private static final int MOCK_SLOT_ID = 1;
+ private static final int MOCK_PORT_ID = 0;
private static final String MOCK_ICCID = "12345";
private static final String MOCK_NICK_NAME = "nick name";
@@ -130,6 +131,12 @@
}
@Override
+ public int onSwitchToSubscriptionWithPort(int slotId, int portIndex, String iccid,
+ boolean forceDeactivateSim) {
+ return 0;
+ }
+
+ @Override
public int onUpdateSubscriptionNickname(int slotId, String iccid, String nickname) {
return 0;
}
@@ -416,6 +423,7 @@
mEuiccServiceBinder.switchToSubscription(
MOCK_SLOT_ID,
+ MOCK_PORT_ID,
MOCK_ICCID,
true /*forceDeactivateSim*/,
new ISwitchToSubscriptionCallback.Stub() {
@@ -423,7 +431,8 @@
public void onComplete(int result) {
assertEquals(EuiccService.RESULT_OK, result);
}
- });
+ },
+ false /* usePortIndex */);
try {
mCountDownLatch.await(CALLBACK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/MockEuiccService.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/MockEuiccService.java
index 52dd47c..698ac75 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/MockEuiccService.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/MockEuiccService.java
@@ -147,6 +147,13 @@
}
@Override
+ public @Result int onSwitchToSubscriptionWithPort(
+ int slotId, int portIndex, @Nullable String iccid, boolean forceDeactivateSim) {
+ sMockEuiccServiceCallback.setMethodCalled();
+ return EuiccService.RESULT_OK;
+ }
+
+ @Override
public int onUpdateSubscriptionNickname(int slotId, String iccid, String nickname) {
sMockEuiccServiceCallback.setMethodCalled();
return EuiccService.RESULT_OK;
diff --git a/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java b/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java
index 0a16299..985aca9 100644
--- a/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import android.app.Instrumentation;
import android.content.pm.PackageManager;
@@ -97,9 +98,7 @@
@Before
public void setUp() throws Exception {
- if (!isFeatureSupported()) {
- return;
- }
+ assumeTrue(isFeatureSupported());
setService(SERVICE_PACKAGE);
setReleaseTime(RELEASE_DEFAULT);
@@ -107,20 +106,12 @@
@Test (expected = SecurityException.class)
public void testPermissions() {
- if (!isFeatureSupported()) {
- throw new SecurityException("Feaure is not supported");
- }
-
runGbaFailCase(TelephonyManager.GBA_FAILURE_REASON_FEATURE_NOT_SUPPORTED,
android.Manifest.permission.READ_PHONE_STATE);
}
@Test
public void testAuthSuccess() {
- if (!isFeatureSupported()) {
- return;
- }
-
Random rand = new Random();
for (int i = 0; i < 20; i++) {
@@ -172,10 +163,6 @@
@Test
public void testGbaNotSupported() throws Exception {
- if (!isFeatureSupported()) {
- return;
- }
-
setService("");
sConfig.setConfig(true, new byte[16], BTID, TelephonyManager.GBA_FAILURE_REASON_UNKNOWN);
@@ -187,10 +174,6 @@
@Test
public void testAuthFail() {
- if (!isFeatureSupported()) {
- return;
- }
-
for (int r = TelephonyManager.GBA_FAILURE_REASON_UNKNOWN;
r <= TelephonyManager.GBA_FAILURE_REASON_SECURITY_PROTOCOL_NOT_SUPPORTED; r++) {
sConfig.setConfig(false, new byte[16], BTID, r);
@@ -235,10 +218,6 @@
@Test
public void testServiceReleaseDefault() throws Exception {
- if (!isFeatureSupported()) {
- return;
- }
-
int ss = sConfig.getServiceState();
boolean isExpected = (ss == TestGbaConfig.STATE_UNKNOWN
|| ss == TestGbaConfig.STATE_REMOVED
@@ -256,10 +235,6 @@
@Test
public void testServiceReleaseDuration() throws Exception {
- if (!isFeatureSupported()) {
- return;
- }
-
int ss = sConfig.getServiceState();
boolean isExpected = (ss == TestGbaConfig.STATE_UNKNOWN
|| ss == TestGbaConfig.STATE_REMOVED
@@ -281,10 +256,6 @@
@Test
public void testServiceNoRelease() throws Exception {
- if (!isFeatureSupported()) {
- return;
- }
-
int ss = sConfig.getServiceState();
boolean isExpected = (ss == TestGbaConfig.STATE_UNKNOWN
|| ss == TestGbaConfig.STATE_REMOVED
@@ -325,7 +296,7 @@
private static boolean isFeatureSupported() {
if (!InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_TELEPHONY)) {
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)) {
return false;
}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallingTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallingTest.java
new file mode 100644
index 0000000..9a53194
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallingTest.java
@@ -0,0 +1,1092 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.cts;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.telecom.Call;
+import android.telecom.PhoneAccount;
+import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.cts.InCallServiceStateValidator;
+import android.telephony.cts.InCallServiceStateValidator.InCallServiceCallbacks;
+import android.telephony.cts.TelephonyUtils;
+import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.stub.ImsFeatureConfiguration;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * CTS tests for ImsCall .
+ */
+@RunWith(AndroidJUnit4.class)
+public class ImsCallingTest {
+
+ private static ImsServiceConnector sServiceConnector;
+
+ private static final String LOG_TAG = "CtsImsCallingTest";
+ private static final String PACKAGE = "android.telephony.ims.cts";
+ private static final String PACKAGE_CTS_DIALER = "android.telephony.cts";
+ private static final String COMMAND_SET_DEFAULT_DIALER = "telecom set-default-dialer ";
+ private static final String COMMAND_GET_DEFAULT_DIALER = "telecom get-default-dialer";
+
+ public static final int WAIT_FOR_SERVICE_TO_UNBOUND = 40000;
+ public static final int WAIT_FOR_CONDITION = 3000;
+ public static final int WAIT_FOR_CALL_STATE = 10000;
+ public static final int WAIT_FOR_CALL_DISCONNECT = 5000;
+ public static final int WAIT_FOR_CALL_CONNECT = 5000;
+ public static final int WAIT_FOR_CALL_STATE_HOLD = 3000;
+ public static final int WAIT_FOR_CALL_STATE_RESUME = 3000;
+ public static final int WAIT_FOR_CALL_STATE_ACTIVE = 15000;
+ public static final int LATCH_WAIT = 0;
+ public static final int LATCH_INCALL_SERVICE_BOUND = 1;
+ public static final int LATCH_INCALL_SERVICE_UNBOUND = 2;
+ public static final int LATCH_IS_ON_CALL_ADDED = 3;
+ public static final int LATCH_IS_ON_CALL_REMOVED = 4;
+ public static final int LATCH_IS_CALL_DIALING = 5;
+ public static final int LATCH_IS_CALL_ACTIVE = 6;
+ public static final int LATCH_IS_CALL_DISCONNECTING = 7;
+ public static final int LATCH_IS_CALL_DISCONNECTED = 8;
+ public static final int LATCH_IS_CALL_RINGING = 9;
+ public static final int LATCH_IS_CALL_HOLDING = 10;
+ public static final int LATCH_MAX = 11;
+
+ private static boolean sIsBound = false;
+ private static int sCounter = 5553639;
+ private static int sTestSlot = 0;
+ private static int sTestSub = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private static long sPreviousOptInStatus = 0;
+ private static long sPreviousEn4GMode = 0;
+ private static String sPreviousDefaultDialer;
+
+ private static CarrierConfigReceiver sReceiver;
+ private static SubscriptionManager sSubcriptionManager;
+
+ private final Object mLock = new Object();
+ private InCallServiceCallbacks mServiceCallBack;
+ private Context mContext;
+ private ConcurrentHashMap<String, Call> mCalls = new ConcurrentHashMap<String, Call>();
+ private String mCurrentCallId = null;
+ private Call mCall1 = null;
+ private Call mCall2 = null;
+ private TestImsCallSessionImpl mCallSession1 = null;
+ private TestImsCallSessionImpl mCallSession2 = null;
+
+ private static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
+ static {
+ for (int i = 0; i < LATCH_MAX; i++) {
+ sLatches[i] = new CountDownLatch(1);
+ }
+ }
+
+ public boolean callingTestLatchCountdown(int latchIndex, int waitMs) {
+ boolean complete = false;
+ try {
+ CountDownLatch latch;
+ synchronized (mLock) {
+ latch = sLatches[latchIndex];
+ }
+ complete = latch.await(waitMs, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ //complete == false
+ }
+ synchronized (mLock) {
+ sLatches[latchIndex] = new CountDownLatch(1);
+ }
+ return complete;
+ }
+
+ public void countDownLatch(int latchIndex) {
+ synchronized (mLock) {
+ sLatches[latchIndex].countDown();
+ }
+ }
+
+ private abstract static class BaseReceiver extends BroadcastReceiver {
+ protected CountDownLatch mLatch = new CountDownLatch(1);
+
+ void clearQueue() {
+ mLatch = new CountDownLatch(1);
+ }
+
+ void waitForChanged() throws Exception {
+ mLatch.await(5000, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ private static class CarrierConfigReceiver extends BaseReceiver {
+ private final int mSubId;
+
+ CarrierConfigReceiver(int subId) {
+ mSubId = subId;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+ int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, -1);
+ if (mSubId == subId) {
+ mLatch.countDown();
+ }
+ }
+ }
+ }
+
+ public interface Condition {
+ Object expected();
+ Object actual();
+ }
+
+ void sleep(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (Exception e) {
+ Log.d(LOG_TAG, "InterruptedException");
+ }
+ }
+
+ void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
+ String description) {
+ final long start = System.currentTimeMillis();
+ while (!Objects.equals(condition.expected(), condition.actual())
+ && System.currentTimeMillis() - start < timeout) {
+ sleep(50);
+ }
+ assertEquals(description, condition.expected(), condition.actual());
+ }
+
+ @BeforeClass
+ public static void beforeAllTests() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ TelephonyManager tm = (TelephonyManager) getContext()
+ .getSystemService(Context.TELEPHONY_SERVICE);
+ sTestSub = ImsUtils.getPreferredActiveSubId();
+ sTestSlot = SubscriptionManager.getSlotIndex(sTestSub);
+ if (tm.getSimState(sTestSlot) != TelephonyManager.SIM_STATE_READY) {
+ return;
+ }
+
+ sServiceConnector = new ImsServiceConnector(InstrumentationRegistry.getInstrumentation());
+ // Remove all live ImsServices until after these tests are done
+ sServiceConnector.clearAllActiveImsServices(sTestSlot);
+
+ sReceiver = new CarrierConfigReceiver(sTestSub);
+ IntentFilter filter = new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+ // ACTION_CARRIER_CONFIG_CHANGED is sticky, so we will get a callback right away.
+ InstrumentationRegistry.getInstrumentation().getContext()
+ .registerReceiver(sReceiver, filter);
+
+ UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ ui.adoptShellPermissionIdentity();
+ // Get the default dialer and save it to restore after test ends.
+ sPreviousDefaultDialer = getDefaultDialer(InstrumentationRegistry.getInstrumentation());
+ // Set dialer as "android.telephony.cts"
+ setDefaultDialer(InstrumentationRegistry.getInstrumentation(), PACKAGE_CTS_DIALER);
+
+ sSubcriptionManager = InstrumentationRegistry.getInstrumentation()
+ .getContext().getSystemService(SubscriptionManager.class);
+ // Get the default Subscription values and save it to restore after test ends.
+ sPreviousOptInStatus = sSubcriptionManager.getLongSubscriptionProperty(sTestSub,
+ SubscriptionManager.VOIMS_OPT_IN_STATUS, 0, getContext());
+ sPreviousEn4GMode = sSubcriptionManager.getLongSubscriptionProperty(sTestSub,
+ SubscriptionManager.ENHANCED_4G_MODE_ENABLED, 0, getContext());
+ // Set the new Sunbscription values
+ sSubcriptionManager.setSubscriptionProperty(sTestSub,
+ SubscriptionManager.VOIMS_OPT_IN_STATUS, String.valueOf(1));
+ sSubcriptionManager.setSubscriptionProperty(sTestSub,
+ SubscriptionManager.ENHANCED_4G_MODE_ENABLED, String.valueOf(1));
+
+ //Override the carrier configurartions
+ CarrierConfigManager configurationManager = InstrumentationRegistry.getInstrumentation()
+ .getContext().getSystemService(CarrierConfigManager.class);
+ PersistableBundle bundle = new PersistableBundle(1);
+ bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_AVAILABLE_BOOL, true);
+ bundle.putBoolean(CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL, true);
+ bundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, false);
+ bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true);
+ bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_IMS_GBA_REQUIRED_BOOL , false);
+
+ sReceiver.clearQueue();
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(configurationManager,
+ (m) -> m.overrideConfig(sTestSub, bundle));
+ } finally {
+ ui.dropShellPermissionIdentity();
+ }
+ sReceiver.waitForChanged();
+ }
+
+ @AfterClass
+ public static void afterAllTests() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ ui.adoptShellPermissionIdentity();
+ // Set the default Sunbscription values.
+ sSubcriptionManager.setSubscriptionProperty(sTestSub,
+ SubscriptionManager.VOIMS_OPT_IN_STATUS, String.valueOf(sPreviousOptInStatus));
+ sSubcriptionManager.setSubscriptionProperty(sTestSub,
+ SubscriptionManager.ENHANCED_4G_MODE_ENABLED, String.valueOf(
+ sPreviousEn4GMode));
+ // Set default dialer
+ setDefaultDialer(InstrumentationRegistry.getInstrumentation(), sPreviousDefaultDialer);
+
+ // Restore all ImsService configurations that existed before the test.
+ if (sServiceConnector != null && sIsBound) {
+ sServiceConnector.disconnectServices();
+ sIsBound = false;
+ }
+ sServiceConnector = null;
+ overrideCarrierConfig(null);
+
+ if (sReceiver != null) {
+ InstrumentationRegistry.getInstrumentation().getContext()
+ .unregisterReceiver(sReceiver);
+ sReceiver = null;
+ }
+ } finally {
+ ui.dropShellPermissionIdentity();
+ }
+ }
+
+ @Before
+ public void beforeTest() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+ TelephonyManager tm = (TelephonyManager) InstrumentationRegistry.getInstrumentation()
+ .getContext().getSystemService(Context.TELEPHONY_SERVICE);
+ if (tm.getSimState(sTestSlot) != TelephonyManager.SIM_STATE_READY) {
+ fail("This test requires that there is a SIM in the device!");
+ }
+ // Correctness check: ensure that the subscription hasn't changed between tests.
+ int[] subs = SubscriptionManager.getSubId(sTestSlot);
+
+ if (subs == null) {
+ fail("This test requires there is an active subscription in slot " + sTestSlot);
+ }
+ boolean isFound = false;
+ for (int sub : subs) {
+ isFound |= (sTestSub == sub);
+ }
+ if (!isFound) {
+ fail("Invalid state found: the test subscription in slot " + sTestSlot + " changed "
+ + "during this test.");
+ }
+ }
+
+ public void bindImsService() throws Exception {
+ // Connect to the ImsService with the MmTel feature.
+ assertTrue(sServiceConnector.connectCarrierImsService(new ImsFeatureConfiguration.Builder()
+ .addFeature(sTestSlot, ImsFeature.FEATURE_MMTEL)
+ .build()));
+ sIsBound = true;
+ // The MmTelFeature is created when the ImsService is bound. If it wasn't created, then the
+ // Framework did not call it.
+ sServiceConnector.getCarrierService().waitForLatchCountdown(
+ TestImsService.LATCH_CREATE_MMTEL);
+ assertNotNull("ImsService created, but ImsService#createMmTelFeature was not called!",
+ sServiceConnector.getCarrierService().getMmTelFeature());
+
+ sServiceConnector.getCarrierService().waitForLatchCountdown(
+ TestImsService.LATCH_MMTEL_CAP_SET);
+
+ MmTelFeature.MmTelCapabilities capabilities = new MmTelFeature.MmTelCapabilities(
+ MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
+ // Set Registered and VoLTE capable
+ sServiceConnector.getCarrierService().getImsService().getRegistration(0).onRegistered(
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+ sServiceConnector.getCarrierService().getMmTelFeature().setCapabilities(capabilities);
+ sServiceConnector.getCarrierService().getMmTelFeature()
+ .notifyCapabilitiesStatusChanged(capabilities);
+
+ // Wait a second for the notifyCapabilitiesStatusChanged indication to be processed on the
+ // main telephony thread - currently no better way of knowing that telephony has processed
+ // this command. SmsManager#isImsSmsSupported() is @hide and must be updated to use new API.
+ Thread.sleep(3000);
+ }
+
+ @After
+ public void afterTest() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ if (!mCalls.isEmpty() && (mCurrentCallId != null)) {
+ Call call = mCalls.get(mCurrentCallId);
+ call.disconnect();
+ }
+
+ if (sServiceConnector != null && sIsBound) {
+ sServiceConnector.disconnectCarrierImsService();
+ sServiceConnector.disconnectServices();
+ sIsBound = false;
+ }
+ }
+
+ @Test
+ public void testOutGoingCall() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ bindImsService();
+ mServiceCallBack = new ServiceCallBack();
+ InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+ TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+ .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+ final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+ Bundle extras = new Bundle();
+
+ // Place outgoing call
+ telecomManager.placeCall(imsUri, extras);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+ Call call = getCall(mCurrentCallId);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+ TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+ .getImsCallsession();
+
+ isCallActive(call, callSession);
+
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+ call.disconnect();
+
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+ isCallDisconnected(call, callSession);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+ waitForUnboundService();
+ }
+
+ @Test
+ public void testOutGoingCallStartFailed() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ bindImsService();
+ mServiceCallBack = new ServiceCallBack();
+ InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+ TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+ .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+ final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+ Bundle extras = new Bundle();
+
+ // Place outgoing call
+ telecomManager.placeCall(imsUri, extras);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+ Call call = getCall(mCurrentCallId);
+
+ waitUntilConditionIsTrueOrTimeout(
+ new Condition() {
+ @Override
+ public Object expected() {
+ return true;
+ }
+
+ @Override
+ public Object actual() {
+ TestMmTelFeature mmtelfeatue = sServiceConnector.getCarrierService()
+ .getMmTelFeature();
+ return (mmtelfeatue.isCallSessionCreated()) ? true : false;
+ }
+ }, WAIT_FOR_CONDITION, "CallSession Created");
+
+ TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+ .getImsCallsession();
+ assertNotNull("Unable to get callSession, its null", callSession);
+ callSession.addTestType(TestImsCallSessionImpl.TEST_TYPE_MO_FAILED);
+
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+ isCallDisconnected(call, callSession);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+ waitForUnboundService();
+ }
+
+ @Test
+ public void testIncomingCall() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+ bindImsService();
+ mServiceCallBack = new ServiceCallBack();
+ InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+ Bundle extras = new Bundle();
+ sServiceConnector.getCarrierService().getMmTelFeature().onIncomingCallReceived(extras);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+ Call call = getCall(mCurrentCallId);
+ if (call.getDetails().getState() == call.STATE_RINGING) {
+ callingTestLatchCountdown(LATCH_WAIT, 5000);
+ call.answer(0);
+ }
+
+ TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+ .getImsCallsession();
+
+ isCallActive(call, callSession);
+
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+ callSession.terminateIncomingCall();
+
+ isCallDisconnected(call, callSession);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+ waitForUnboundService();
+ }
+
+ @Test
+ public void testOutGoingCallForExecutor() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ sServiceConnector.setExecutorTestType(true);
+ bindImsService();
+
+ mServiceCallBack = new ServiceCallBack();
+ InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+ TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+ .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+ final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+ Bundle extras = new Bundle();
+
+ // Place outgoing call
+ telecomManager.placeCall(imsUri, extras);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+ Call call = getCall(mCurrentCallId);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+ TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+ .getImsCallsession();
+
+ isCallActive(call, callSession);
+
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+ call.disconnect();
+
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+ isCallDisconnected(call, callSession);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+ waitForUnboundService();
+ }
+
+ @Test
+ public void testOutGoingCallHoldResume() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ bindImsService();
+ mServiceCallBack = new ServiceCallBack();
+ InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+ TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+ .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+ final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+ Bundle extras = new Bundle();
+
+ // Place outgoing call
+ telecomManager.placeCall(imsUri, extras);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+ Call call = getCall(mCurrentCallId);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+ TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+ .getImsCallsession();
+
+ isCallActive(call, callSession);
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_HOLD);
+ // Put on hold
+ call.hold();
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_RESUME);
+ // Put on resume
+ call.unhold();
+ isCallActive(call, callSession);
+
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+ call.disconnect();
+
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+ isCallDisconnected(call, callSession);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+ waitForUnboundService();
+ }
+
+ @Test
+ public void testOutGoingCallHoldFailure() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ bindImsService();
+ mServiceCallBack = new ServiceCallBack();
+ InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+ TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+ .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+ final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+ Bundle extras = new Bundle();
+
+ // Place outgoing call
+ telecomManager.placeCall(imsUri, extras);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+ Call call = getCall(mCurrentCallId);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+ TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+ .getImsCallsession();
+
+ isCallActive(call, callSession);
+ callSession.addTestType(TestImsCallSessionImpl.TEST_TYPE_HOLD_FAILED);
+
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_HOLD);
+ call.hold();
+ assertTrue("call is not in Active State", (call.getDetails().getState()
+ == call.STATE_ACTIVE));
+
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+ call.disconnect();
+
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+ isCallDisconnected(call, callSession);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+ waitForUnboundService();
+ }
+
+
+ @Test
+ public void testOutGoingCallResumeFailure() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ bindImsService();
+ mServiceCallBack = new ServiceCallBack();
+ InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+ TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+ .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+ final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+ Bundle extras = new Bundle();
+
+ // Place outgoing call
+ telecomManager.placeCall(imsUri, extras);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+ Call call = getCall(mCurrentCallId);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+ TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+ .getImsCallsession();
+
+ isCallActive(call, callSession);
+
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_HOLD);
+ // Put on hold
+ call.hold();
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+
+ callSession.addTestType(TestImsCallSessionImpl.TEST_TYPE_RESUME_FAILED);
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_RESUME);
+ call.unhold();
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE);
+ assertTrue("Call is not in Hold State", (call.getDetails().getState()
+ == call.STATE_HOLDING));
+
+
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+ call.disconnect();
+
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+ isCallDisconnected(call, callSession);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+ waitForUnboundService();
+ }
+
+ @Test
+ public void testOutGoingIncomingMultiCall() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ bindImsService();
+ mServiceCallBack = new ServiceCallBack();
+ InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+ TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+ .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+ final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+ Bundle extras = new Bundle();
+
+ // Place outgoing call
+ telecomManager.placeCall(imsUri, extras);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+ Call moCall = getCall(mCurrentCallId);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+ TestImsCallSessionImpl moCallSession = sServiceConnector.getCarrierService()
+ .getMmTelFeature().getImsCallsession();
+ isCallActive(moCall, moCallSession);
+ assertTrue("Call is not in Active State", (moCall.getDetails().getState()
+ == Call.STATE_ACTIVE));
+
+ extras.putBoolean("android.telephony.ims.feature.extra.IS_USSD", false);
+ extras.putBoolean("android.telephony.ims.feature.extra.IS_UNKNOWN_CALL", false);
+ extras.putString("android:imsCallID", String.valueOf(++sCounter));
+ extras.putLong("android:phone_id", 123456);
+ sServiceConnector.getCarrierService().getMmTelFeature().onIncomingCallReceived(extras);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+ Call mtCall = null;
+ if (mCurrentCallId != null) {
+ mtCall = getCall(mCurrentCallId);
+ if (mtCall.getDetails().getState() == Call.STATE_RINGING) {
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_CONNECT);
+ mtCall.answer(0);
+ }
+ }
+
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+ TestImsCallSessionImpl mtCallSession = sServiceConnector.getCarrierService()
+ .getMmTelFeature().getImsCallsession();
+ isCallActive(mtCall, mtCallSession);
+ assertTrue("Call is not in Active State", (mtCall.getDetails().getState()
+ == Call.STATE_ACTIVE));
+
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+ mtCall.disconnect();
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+ isCallDisconnected(mtCall, mtCallSession);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+ isCallActive(moCall, moCallSession);
+ assertTrue("Call is not in Active State", (moCall.getDetails().getState()
+ == Call.STATE_ACTIVE));
+
+ moCall.disconnect();
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+ isCallDisconnected(moCall, moCallSession);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+ waitForUnboundService();
+ }
+
+ @Test
+ public void testOutGoingCallSwap() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ bindImsService();
+ mServiceCallBack = new ServiceCallBack();
+ InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+ addOutgoingCalls();
+
+ // Swap the call
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_RESUME);
+ mCall1.unhold();
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+ assertTrue("Call is not in Hold State", (mCall2.getDetails().getState()
+ == Call.STATE_HOLDING));
+ isCallActive(mCall1, mCallSession1);
+
+ // After successful call swap disconnect the call
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+ mCall1.disconnect();
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+ isCallDisconnected(mCall1, mCallSession1);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+ //Wait till second call is in active state
+ waitUntilConditionIsTrueOrTimeout(
+ new Condition() {
+ @Override
+ public Object expected() {
+ return true;
+ }
+
+ @Override
+ public Object actual() {
+ return (mCall2.getDetails().getState() == Call.STATE_ACTIVE)
+ ? true : false;
+ }
+ }, WAIT_FOR_CALL_STATE_ACTIVE, "Call in Active State");
+
+ isCallActive(mCall2, mCallSession2);
+ mCall2.disconnect();
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+ isCallDisconnected(mCall2, mCallSession2);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+ resetCallSessionObjects();
+ waitForUnboundService();
+ }
+
+ @Test
+ public void testOutGoingCallSwapFail() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ bindImsService();
+ mServiceCallBack = new ServiceCallBack();
+ InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+ addOutgoingCalls();
+
+ mCallSession1.addTestType(TestImsCallSessionImpl.TEST_TYPE_RESUME_FAILED);
+ // Swap the call
+ callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_RESUME);
+ mCall1.unhold();
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+ assertTrue("Call is not in Hold State", (mCall1.getDetails().getState()
+ == Call.STATE_HOLDING));
+
+ // Wait till second call is in active state
+ waitUntilConditionIsTrueOrTimeout(
+ new Condition() {
+ @Override
+ public Object expected() {
+ return true;
+ }
+
+ @Override
+ public Object actual() {
+ return (mCall2.getDetails().getState() == Call.STATE_ACTIVE)
+ ? true : false;
+ }
+ }, WAIT_FOR_CALL_STATE_ACTIVE, "Call in Active State");
+
+ isCallActive(mCall2, mCallSession2);
+ mCallSession1.removeTestType(TestImsCallSessionImpl.TEST_TYPE_RESUME_FAILED);
+ mCall2.disconnect();
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+ isCallDisconnected(mCall2, mCallSession2);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+ // Wait till second call is in active state
+ isCallActive(mCall1, mCallSession1);
+ mCall1.disconnect();
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+ isCallDisconnected(mCall1, mCallSession1);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+ resetCallSessionObjects();
+ waitForUnboundService();
+ }
+
+ private void addOutgoingCalls() throws Exception {
+ TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+ .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+ // Place first outgoing call
+ final Uri imsUri1 = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter),
+ null);
+ Bundle extras1 = new Bundle();
+
+ telecomManager.placeCall(imsUri1, extras1);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+ mCall1 = getCall(mCurrentCallId);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+ mCallSession1 = sServiceConnector.getCarrierService().getMmTelFeature().getImsCallsession();
+ isCallActive(mCall1, mCallSession1);
+ assertTrue("Call is not in Active State", (mCall1.getDetails().getState()
+ == Call.STATE_ACTIVE));
+
+ // Place second outgoing call
+ final Uri imsUri2 = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter),
+ null);
+ Bundle extras2 = new Bundle();
+
+ telecomManager.placeCall(imsUri2, extras2);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+ mCall2 = getCall(mCurrentCallId);
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+ assertTrue("Call is not in Hold State", (mCall1.getDetails().getState()
+ == Call.STATE_HOLDING));
+
+ //Wait till the object of TestImsCallSessionImpl for second call created.
+ waitUntilConditionIsTrueOrTimeout(
+ new Condition() {
+ @Override
+ public Object expected() {
+ return true;
+ }
+
+ @Override
+ public Object actual() {
+ TestMmTelFeature mmtelfeatue = sServiceConnector.getCarrierService()
+ .getMmTelFeature();
+ return (mmtelfeatue.getImsCallsession() != mCallSession1) ? true : false;
+ }
+ }, WAIT_FOR_CONDITION, "CallSession Created");
+
+ mCallSession2 = sServiceConnector.getCarrierService().getMmTelFeature().getImsCallsession();
+ isCallActive(mCall2, mCallSession2);
+ assertTrue("Call is not in Active State", (mCall2.getDetails().getState()
+ == Call.STATE_ACTIVE));
+ }
+
+ private void resetCallSessionObjects() {
+ mCall1 = mCall2 = null;
+ mCallSession1 = mCallSession2 = null;
+ }
+
+ public void waitForUnboundService() {
+ waitUntilConditionIsTrueOrTimeout(
+ new Condition() {
+ @Override
+ public Object expected() {
+ return true;
+ }
+
+ @Override
+ public Object actual() {
+ InCallServiceStateValidator inCallService = mServiceCallBack.getService();
+ return (inCallService.isServiceUnBound()) ? true : false;
+ }
+ }, WAIT_FOR_SERVICE_TO_UNBOUND, "Service Unbound");
+ }
+
+ public void isCallActive(Call call, TestImsCallSessionImpl callsession) {
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_ACTIVE, WAIT_FOR_CALL_STATE));
+ assertNotNull("Unable to get callSession, its null", callsession);
+
+ waitUntilConditionIsTrueOrTimeout(
+ new Condition() {
+ @Override
+ public Object expected() {
+ return true;
+ }
+
+ @Override
+ public Object actual() {
+ return (callsession.isInCall()) ? true : false;
+ }
+ }, WAIT_FOR_CONDITION, "Call Active");
+ }
+
+ public void isCallDisconnected(Call call, TestImsCallSessionImpl callsession) {
+ assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTED, WAIT_FOR_CALL_STATE));
+ assertNotNull("Unable to get callSession, its null", callsession);
+
+ waitUntilConditionIsTrueOrTimeout(
+ new Condition() {
+ @Override
+ public Object expected() {
+ return true;
+ }
+
+ @Override
+ public Object actual() {
+ return (callsession.isInTerminated()) ? true : false;
+ }
+ }, WAIT_FOR_CONDITION, "Call Disconnected");
+ }
+
+ private void setCallID(String callid) {
+ assertNotNull("Call Id is set to null", callid);
+ mCurrentCallId = callid;
+ }
+
+ public void addCall(Call call) {
+ String callid = getCallId(call);
+ setCallID(callid);
+ synchronized (mCalls) {
+ mCalls.put(callid, call);
+ }
+ }
+
+ public String getCallId(Call call) {
+ String str = call.toString();
+ String[] arrofstr = str.split(",", 3);
+ int index = arrofstr[0].indexOf(":");
+ String callId = arrofstr[0].substring(index + 1);
+ return callId;
+ }
+
+ public Call getCall(String callId) {
+ synchronized (mCalls) {
+ if (mCalls.isEmpty()) {
+ return null;
+ }
+
+ for (Map.Entry<String, Call> entry : mCalls.entrySet()) {
+ if (entry.getKey().equals(callId)) {
+ Call call = entry.getValue();
+ assertNotNull("Call is not added, its null", call);
+ return call;
+ }
+ }
+ }
+ return null;
+ }
+
+ private void removeCall(Call call) {
+ if (mCalls.isEmpty()) {
+ return;
+ }
+
+ String callid = getCallId(call);
+ Map.Entry<String, Call>[] entries = mCalls.entrySet().toArray(new Map.Entry[mCalls.size()]);
+ for (Map.Entry<String, Call> entry : entries) {
+ if (entry.getKey().equals(callid)) {
+ mCalls.remove(entry.getKey());
+ mCurrentCallId = null;
+ }
+ }
+ }
+
+ class ServiceCallBack extends InCallServiceCallbacks {
+
+ @Override
+ public void onCallAdded(Call call, int numCalls) {
+ Log.i(LOG_TAG, "onCallAdded, Call: " + call + ", Num Calls: " + numCalls);
+ addCall(call);
+ countDownLatch(LATCH_IS_ON_CALL_ADDED);
+ }
+
+ @Override
+ public void onCallRemoved(Call call, int numCalls) {
+ Log.i(LOG_TAG, "onCallRemoved, Call: " + call + ", Num Calls: " + numCalls);
+ removeCall(call);
+ countDownLatch(LATCH_IS_ON_CALL_REMOVED);
+ }
+
+ @Override
+ public void onCallStateChanged(Call call, int state) {
+ Log.i(LOG_TAG, "onCallStateChanged " + state + "Call: " + call);
+
+ switch(state) {
+ case Call.STATE_DIALING : {
+ countDownLatch(LATCH_IS_CALL_DIALING);
+ break;
+ }
+ case Call.STATE_ACTIVE : {
+ countDownLatch(LATCH_IS_CALL_ACTIVE);
+ break;
+ }
+ case Call.STATE_DISCONNECTING : {
+ countDownLatch(LATCH_IS_CALL_DISCONNECTING);
+ break;
+ }
+ case Call.STATE_DISCONNECTED : {
+ countDownLatch(LATCH_IS_CALL_DISCONNECTED);
+ break;
+ }
+ case Call.STATE_RINGING : {
+ countDownLatch(LATCH_IS_CALL_RINGING);
+ break;
+ }
+ case Call.STATE_HOLDING : {
+ countDownLatch(LATCH_IS_CALL_HOLDING);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ private static Context getContext() {
+ return InstrumentationRegistry.getInstrumentation().getContext();
+ }
+
+ private static String setDefaultDialer(Instrumentation instrumentation, String packageName)
+ throws Exception {
+ String str = TelephonyUtils.executeShellCommand(instrumentation, COMMAND_SET_DEFAULT_DIALER
+ + packageName);
+ return str;
+ }
+
+ private static String getDefaultDialer(Instrumentation instrumentation) throws Exception {
+ String str = TelephonyUtils.executeShellCommand(instrumentation,
+ COMMAND_GET_DEFAULT_DIALER);
+ return str;
+ }
+
+ private static void overrideCarrierConfig(PersistableBundle bundle) throws Exception {
+ CarrierConfigManager carrierConfigManager = InstrumentationRegistry.getInstrumentation()
+ .getContext().getSystemService(CarrierConfigManager.class);
+ sReceiver.clearQueue();
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(carrierConfigManager,
+ (m) -> m.overrideConfig(sTestSub, bundle));
+ sReceiver.waitForChanged();
+ }
+
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java
index ae6306a..a994d98 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java
@@ -81,6 +81,12 @@
private static final String COMMAND_SET_TEST_MODE_ENABLED = "src set-test-enabled ";
private static final String COMMAND_SET_D2D_ENABLED = "d2d set-device-support ";
+ private boolean mIsTestTypeExecutor = false;
+
+ public void setExecutorTestType(boolean type) {
+ mIsTestTypeExecutor = type;
+ }
+
private class TestCarrierServiceConnection implements ServiceConnection {
private final CountDownLatch mLatch;
@@ -117,7 +123,7 @@
@Override
public void onServiceDisconnected(ComponentName name) {
- mCarrierService = null;
+ mExternalService = null;
}
}
@@ -143,11 +149,13 @@
mIsServiceOverridden = true;
switch (mConnectionType) {
case CONNECTION_TYPE_IMS_SERVICE_CARRIER: {
- setCarrierImsService("none");
+ boolean unbindSent = setCarrierImsService("none");
+ if (unbindSent) waitForCarrierPackageUnbind();
break;
}
case CONNECTION_TYPE_IMS_SERVICE_DEVICE: {
- setDeviceImsService("");
+ boolean unbindSent = setDeviceImsService("");
+ if (unbindSent) waitForDevicePackageUnbind();
break;
}
case CONNECTION_TYPE_DEFAULT_SMS_APP: {
@@ -157,6 +165,35 @@
}
}
+ void waitForCarrierPackageUnbind() {
+ TestImsService carrierService = getCarrierService();
+ if (carrierService == null) return;
+ // First unbind the local services
+ removeLocalCarrierServiceConnection();
+ // Then wait for AOSP to unbind if there is still an active binding.
+ boolean isBound = carrierService.isTelephonyBound();
+ if (ImsUtils.VDBG) Log.i(TAG, "waitForCarrierPackageUnbind: isBound=" + isBound);
+ if (isBound) {
+ // Wait for telephony to unbind to local ImsService
+ carrierService.waitForLatchCountdown(TestImsService.LATCH_ON_UNBIND);
+ }
+ }
+
+ void waitForDevicePackageUnbind() throws Exception {
+ // Wait until the ImsService unbinds
+ ITestExternalImsService externalService = getExternalService();
+ if (externalService == null) return;
+ // First unbind the local services
+ removeLocalExternalServiceConnection();
+ // Then wait for AOSP to unbind if there is still an active binding.
+ boolean isBound = externalService.isTelephonyBound();
+ if (ImsUtils.VDBG) Log.i(TAG, "waitForDevicePackageUnbind: isBound=" + isBound);
+ if (isBound) {
+ // Wait for telephony to unbind to external ImsService
+ externalService.waitForLatchCountdown(TestImsService.LATCH_ON_UNBIND);
+ }
+ }
+
boolean overrideService(ImsFeatureConfiguration config) throws Exception {
mIsServiceOverridden = true;
switch (mConnectionType) {
@@ -480,6 +517,11 @@
return false;
}
mCarrierService.resetState();
+ if (mIsTestTypeExecutor) {
+ mCarrierService.setExecutorTestType(mIsTestTypeExecutor);
+ // reset the mIsTestTypeExecutor value
+ mIsTestTypeExecutor = false;
+ }
return true;
}
@@ -559,17 +601,27 @@
}
}
- // Detect and disconnect all active services.
- void disconnectServices() throws Exception {
- // Remove local connection
+ void removeLocalCarrierServiceConnection() {
if (mCarrierServiceConn != null) {
mInstrumentation.getContext().unbindService(mCarrierServiceConn);
+ mCarrierServiceConn = null;
mCarrierService = null;
}
+ }
+
+ void removeLocalExternalServiceConnection() {
if (mExternalServiceConn != null) {
mInstrumentation.getContext().unbindService(mExternalServiceConn);
+ mExternalServiceConn = null;
mExternalService = null;
}
+ }
+
+ // Detect and disconnect all active services.
+ void disconnectServices() throws Exception {
+ // Remove local connections
+ removeLocalCarrierServiceConnection();
+ removeLocalExternalServiceConnection();
mDeviceServiceConnection.restoreOriginalPackage();
mCarrierServiceConnection.restoreOriginalPackage();
mDefaultSmsAppConnection.restoreOriginalPackage();
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
index 3c1e614..09ea8d8 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
@@ -41,9 +41,9 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.cts.AsyncSmsMessageListener;
+import android.telephony.cts.CarrierCapability;
import android.telephony.cts.SmsReceiverHelper;
import android.telephony.cts.TelephonyUtils;
-import android.telephony.cts.CarrierCapability;
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsManager;
import android.telephony.ims.ImsMmTelManager;
@@ -87,14 +87,13 @@
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 java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
-import android.util.Log;
-
/**
* CTS tests for ImsService API.
*/
@@ -366,7 +365,7 @@
if (!ImsUtils.shouldTestImsService()) {
return;
}
- // Unbind the GTS ImsService after the test completes.
+ // Unbind the ImsService after the test completes.
if (sServiceConnector != null) {
sServiceConnector.setSingleRegistrationTestModeEnabled(false);
sServiceConnector.disconnectCarrierImsService();
@@ -389,6 +388,26 @@
TestImsService.LATCH_CREATE_RCS);
assertNotNull("ImsService created, but ImsService#createRcsFeature was not called!",
sServiceConnector.getCarrierService().getRcsFeature());
+ assertTrue("Not expected subId received!",
+ isExpectedSubId(sServiceConnector.getCarrierService().getSubIDs()));
+ }
+
+ @Test
+ public void testCarrierImsServiceBindRcsFeatureForExecutor() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+ sServiceConnector.setExecutorTestType(true);
+ // Connect to the ImsService with the RCS feature.
+ assertTrue(sServiceConnector.connectCarrierImsService(new ImsFeatureConfiguration.Builder()
+ .addFeature(sTestSlot, ImsFeature.FEATURE_RCS)
+ .build()));
+ // The RcsFeature is created when the ImsService is bound. If it wasn't created, then the
+ // Framework did not call it.
+ sServiceConnector.getCarrierService().waitForLatchCountdown(
+ TestImsService.LATCH_CREATE_RCS);
+ assertNotNull("ImsService created, but ImsService#createRcsFeature was not called!",
+ sServiceConnector.getCarrierService().getRcsFeature());
}
@Test
@@ -409,6 +428,8 @@
// Wait for the framework to set the capabilities on the ImsService
sServiceConnector.getCarrierService().waitForLatchCountdown(
TestImsService.LATCH_MMTEL_CAP_SET);
+ assertTrue("Not expected subId received!",
+ isExpectedSubId(sServiceConnector.getCarrierService().getSubIDs()));
}
@Test
@@ -438,6 +459,8 @@
assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
TestImsService.LATCH_DISABLE_IMS));
assertFalse(sServiceConnector.getCarrierService().isEnabled());
+ assertTrue("Not expected subId received!",
+ isExpectedSubId(sServiceConnector.getCarrierService().getSubIDs()));
}
@Test
@@ -466,6 +489,41 @@
// Wait for the framework to set the capabilities on the ImsService
sServiceConnector.getCarrierService().waitForLatchCountdown(
TestImsService.LATCH_MMTEL_CAP_SET);
+ assertTrue("Not expected subId received!",
+ isExpectedSubId(sServiceConnector.getCarrierService().getSubIDs()));
+ }
+
+ @Test
+ public void testCarrierImsServiceBindRcsChangeToMmtelCompat() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+ // Connect to the ImsService with the RCS feature.
+ ImsFeatureConfiguration config = new ImsFeatureConfiguration.Builder()
+ .addFeature(sTestSlot, ImsFeature.FEATURE_RCS)
+ .build();
+ assertTrue(sServiceConnector.connectCarrierImsServiceLocally());
+ sServiceConnector.getCarrierService().resetState();
+ // Set the flag for ImsService compatibility test.
+ sServiceConnector.getCarrierService().setImsServiceCompat();
+ assertTrue(sServiceConnector.triggerFrameworkConnectionToCarrierImsService(config));
+ // The RcsFeature is created when the ImsService is bound. If it wasn't created, then the
+ // Framework did not call it.
+ assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+ TestImsService.LATCH_CREATE_RCS));
+
+ // Change the supported feature to MMTEl
+ sServiceConnector.getCarrierService().getImsServiceCompat().onUpdateSupportedImsFeatures(
+ new ImsFeatureConfiguration.Builder()
+ .addFeature(sTestSlot, ImsFeature.FEATURE_MMTEL).build());
+
+ // createMmTelFeature should be called.
+ assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+ TestImsService.LATCH_CREATE_MMTEL));
+
+ // Wait for the framework to set the capabilities on the ImsService
+ sServiceConnector.getCarrierService().waitForLatchCountdown(
+ TestImsService.LATCH_MMTEL_CAP_SET);
}
@Test
@@ -503,6 +561,8 @@
// Wait for the framework to set the capabilities on the ImsService
sServiceConnector.getCarrierService().waitForLatchCountdown(
TestImsService.LATCH_MMTEL_CAP_SET);
+ assertTrue("Not expected subId received!",
+ isExpectedSubId(sServiceConnector.getCarrierService().getSubIDs()));
}
@Test
@@ -4378,4 +4438,8 @@
throw new RuntimeException("Invalid hex char '" + c + "'");
}
+
+ private boolean isExpectedSubId(HashSet<Integer> subIDs) {
+ return (subIDs.size() == 1) && subIDs.contains(sTestSub);
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
index 82a2f2a..b53d13e 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
@@ -59,18 +59,13 @@
public static boolean shouldTestImsService() {
final PackageManager pm = InstrumentationRegistry.getInstrumentation().getContext()
.getPackageManager();
- boolean hasTelephony = pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
- boolean hasIms = pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS);
- return hasTelephony && hasIms;
+ return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS);
}
public static boolean shouldTestImsSingleRegistration() {
final PackageManager pm = InstrumentationRegistry.getInstrumentation().getContext()
.getPackageManager();
- boolean hasIms = pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS);
- boolean hasSingleReg = pm.hasSystemFeature(
- PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION);
- return hasIms && hasSingleReg;
+ return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION);
}
public static int getPreferredActiveSubId() {
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java
index d4386ff..8f4a4104 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java
@@ -1135,6 +1135,149 @@
overrideCarrierConfig(null);
}
+ /**
+ * Tests the case when contact1 has had a successful network query, but a query for contact2
+ * has resulted in the carrier network not responding with a NOTIFY. If contact1 caps are
+ * queried again, the query to the cache for contact1 should not be blocked in a queue behind
+ * the pending network query. Eventually, request for contact2 will timeout with onTimeout
+ * response from vendor, which will result in ERROR_REQUEST_TIMEOUT result back to app.
+ */
+ @Test
+ public void testCacheQuerySuccessWhenNetworkBlocked() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+ ImsManager imsManager = getContext().getSystemService(ImsManager.class);
+ RcsUceAdapter uceAdapter = imsManager.getImsRcsManager(sTestSub).getUceAdapter();
+ assertNotNull("UCE adapter should not be null!", uceAdapter);
+
+ // Remove the test contact capabilities
+ removeTestContactFromEab();
+
+ // Connect to the ImsService
+ setupTestImsService(uceAdapter, true, true /* presence cap */, false /* OPTIONS */);
+
+ TestRcsCapabilityExchangeImpl capabilityExchangeImpl = sServiceConnector
+ .getCarrierService().getRcsFeature().getRcsCapabilityExchangeImpl();
+
+ BlockingQueue<Boolean> completeQueue = new LinkedBlockingQueue<>();
+ BlockingQueue<RcsContactUceCapability> capabilityQueue = new LinkedBlockingQueue<>();
+ BlockingQueue<Integer> errorQueue = new LinkedBlockingQueue<>();
+ RcsUceAdapter.CapabilitiesCallback callback = new RcsUceAdapter.CapabilitiesCallback() {
+ @Override
+ public void onCapabilitiesReceived(List<RcsContactUceCapability> capabilities) {
+ capabilities.forEach(capabilityQueue::offer);
+ }
+ @Override
+ public void onComplete() {
+ completeQueue.offer(true);
+ }
+ @Override
+ public void onError(int errorCode, long retryAfterMilliseconds) {
+ errorQueue.offer(errorCode);
+ }
+ };
+
+ // Prepare two contacts
+ final Uri contact1 = sTestNumberUri;
+ final Uri contact2 = sTestContact2Uri;
+
+ Collection<Uri> contacts = new ArrayList<>(1);
+ contacts.add(contact1);
+
+ ArrayList<String> pidfXmlList = new ArrayList<>(1);
+ pidfXmlList.add(getPidfXmlData(contact1, true, true));
+
+ // Setup the network response is 200 OK and notify capabilities update
+ int networkRespCode = 200;
+ String networkRespReason = "OK";
+ capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
+ cb.onNetworkResponse(networkRespCode, networkRespReason);
+ cb.onNotifyCapabilitiesUpdate(pidfXmlList);
+ cb.onTerminated("", 0L);
+ });
+
+ requestCapabilities(uceAdapter, contacts, callback);
+
+ List<RcsContactUceCapability> resultCapList = new ArrayList<>();
+
+ // Verify that the first contact is updated
+ RcsContactUceCapability capability = waitForResult(capabilityQueue);
+ assertNotNull("Cannot receive the first capabilities result.", capability);
+ resultCapList.add(capability);
+
+ // Verify contact1's capabilities from the received capabilities list
+ RcsContactUceCapability resultCapability = getContactCapability(resultCapList, contact1);
+ assertNotNull("Cannot find the contact: " + contact1, resultCapability);
+ verifyCapabilityResult(resultCapability, contact1, SOURCE_TYPE_NETWORK,
+ REQUEST_RESULT_FOUND, true, true);
+
+ // Verify the onCompleted is called
+ waitForResult(completeQueue);
+
+ completeQueue.clear();
+ capabilityQueue.clear();
+ resultCapList.clear();
+
+ // Now hold the second contact and do not return a response until after contact1 is queried
+ //again
+ CountDownLatch latch = new CountDownLatch(1);
+ capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
+ try {
+ cb.onNetworkResponse(networkRespCode, networkRespReason);
+ assertTrue("Timed out waiting for latch", latch.await(10, TimeUnit.SECONDS));
+ // We didn't receive any NOTIFY
+ cb.onTerminated("timeout", 0L);
+ } catch (InterruptedException e) {
+ fail("Waiting for cap response resulted in unexpected exception: " + e);
+ }
+ });
+
+ contacts.clear();
+ contacts.add(contact2);
+
+ pidfXmlList.clear();
+ pidfXmlList.add(getPidfXmlData(contact2, true, true));
+
+ requestCapabilities(uceAdapter, contacts, callback);
+
+ // Send another request for contact1's caps. Although the request queue is blocked due to
+ // pending network request, contact1 has valid caps, so system should return those.
+ contacts.clear();
+ contacts.add(contact1);
+
+ pidfXmlList.clear();
+ pidfXmlList.add(getPidfXmlData(contact1, true, true));
+
+ requestCapabilities(uceAdapter, contacts, callback);
+
+ capability = waitForResult(capabilityQueue);
+ assertNotNull("Cannot receive the cached capabilities result.", capability);
+ resultCapList.add(capability);
+
+ // Verify contact1's capabilities from the received capabilities list
+ resultCapability = getContactCapability(resultCapList, contact1);
+ assertNotNull("Cannot find the contact: " + contact1, resultCapability);
+ verifyCapabilityResult(resultCapability, contact1, SOURCE_TYPE_CACHED, REQUEST_RESULT_FOUND,
+ true, true);
+
+ // Now contact2's query finishes and it timed out without a NOTIFY
+ latch.countDown();
+
+ Integer error = waitForResult(errorQueue);
+ assertNotNull("Cannot receive the expected error result.", capability);
+ assertEquals("Timeout without NOTIFY should result in ERROR_REQUEST_TIMEOUT",
+ RcsUceAdapter.ERROR_REQUEST_TIMEOUT, error.intValue());
+
+ errorQueue.clear();
+ completeQueue.clear();
+ capabilityQueue.clear();
+ resultCapList.clear();
+ removeTestContactFromEab();
+
+ overrideCarrierConfig(null);
+ }
+
@Test
public void testRequestCapabilitiesFromCacheWithPresenceMechanism() throws Exception {
if (!ImsUtils.shouldTestImsService()) {
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsCallSessionImpl.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsCallSessionImpl.java
new file mode 100644
index 0000000..1dba231
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsCallSessionImpl.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.cts;
+
+import static org.junit.Assert.fail;
+
+import android.os.Bundle;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsCallSessionListener;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsStreamMediaProfile;
+import android.telephony.ims.stub.ImsCallSessionImplBase;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+public class TestImsCallSessionImpl extends ImsCallSessionImplBase {
+
+ private static final String LOG_TAG = "CtsTestImsCallSessionImpl";
+ private static final int LATCH_WAIT = 0;
+ private static final int LATCH_MAX = 1;
+ private static final int WAIT_FOR_STATE_CHANGE = 2000;
+ private static final int WAIT_FOR_ESTABLISHING = 5000;
+
+ private final String mCallId = String.valueOf(this.hashCode());
+ private final Object mLock = new Object();
+
+ private int mState = ImsCallSessionImplBase.State.IDLE;
+ private ImsCallProfile mCallProfile;
+ private ImsCallProfile mLocalCallProfile;
+ private ImsCallSessionListener mListener;
+
+ private final MessageExecutor mCallExecutor = new MessageExecutor("CallExecutor");
+ private final MessageExecutor mCallBackExecutor = new MessageExecutor("CallBackExecutor");
+ private static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
+ static {
+ for (int i = 0; i < LATCH_MAX; i++) {
+ sLatches[i] = new CountDownLatch(1);
+ }
+ }
+
+ public static final int TEST_TYPE_NONE = 0x00000000;
+ public static final int TEST_TYPE_MO_ANSWER = 0x00000001;
+ public static final int TEST_TYPE_MO_FAILED = 0x00000002;
+ public static final int TEST_TYPE_HOLD_FAILED = 0x00000004;
+ public static final int TEST_TYPE_RESUME_FAILED = 0x00000008;
+
+ private int mTestType = TEST_TYPE_NONE;
+
+ public boolean imsCallSessionLatchCountdown(int latchIndex, int waitMs) {
+ boolean complete = false;
+ try {
+ CountDownLatch latch;
+ synchronized (mLock) {
+ latch = sLatches[latchIndex];
+ }
+ complete = latch.await(waitMs, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ //complete == false
+ }
+ synchronized (mLock) {
+ sLatches[latchIndex] = new CountDownLatch(1);
+ }
+ return complete;
+ }
+
+ public void countDownLatch(int latchIndex) {
+ synchronized (mLock) {
+ sLatches[latchIndex].countDown();
+ }
+ }
+
+ public TestImsCallSessionImpl(ImsCallProfile profile) {
+ mCallProfile = profile;
+ }
+
+ @Override
+ public String getCallId() {
+ return mCallId;
+ }
+
+ @Override
+ public ImsCallProfile getCallProfile() {
+ return mCallProfile;
+ }
+
+ @Override
+ public ImsCallProfile getLocalCallProfile() {
+ return mLocalCallProfile;
+ }
+
+ @Override
+ public int getState() {
+ return mState;
+ }
+
+ @Override
+ public boolean isInCall() {
+ return (mState == ImsCallSessionImplBase.State.ESTABLISHED) ? true : false;
+ }
+
+ @Override
+ public void setListener(ImsCallSessionListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void start(String callee, ImsCallProfile profile) {
+ mLocalCallProfile = profile;
+ int state = getState();
+
+ if ((state != ImsCallSessionImplBase.State.IDLE)
+ || (state != ImsCallSessionImplBase.State.INITIATED)) {
+ Log.d(LOG_TAG, "start :: Illegal state; callId= " + getCallId()
+ + ", state=" + getState());
+ }
+
+ mCallExecutor.execute(() -> {
+ imsCallSessionLatchCountdown(LATCH_WAIT, 500);
+ if (isTestType(TEST_TYPE_MO_FAILED)) {
+ startFailed();
+ } else {
+ startInternal();
+ }
+ });
+ }
+
+ void startInternal() {
+ imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_STATE_CHANGE);
+ postAndRunTask(() -> {
+ try {
+ if (mListener == null) {
+ return;
+ }
+ Log.d(LOG_TAG, "invokeInitiating mCallId = " + mCallId);
+ mListener.callSessionInitiating(mCallProfile);
+ } catch (Throwable t) {
+ Throwable cause = t.getCause();
+ if (t instanceof DeadObjectException
+ || (cause != null && cause instanceof DeadObjectException)) {
+ fail("starting cause Throwable to be thrown: " + t);
+ }
+ }
+ });
+ setState(ImsCallSessionImplBase.State.INITIATED);
+
+ ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
+ ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
+ ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE,
+ ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+ ImsStreamMediaProfile.DIRECTION_INVALID,
+ ImsStreamMediaProfile.RTT_MODE_DISABLED);
+
+ ImsCallProfile profile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
+ ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
+ mCallProfile.updateMediaProfile(profile);
+
+ imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_STATE_CHANGE);
+ postAndRunTask(() -> {
+ try {
+ if (mListener == null) {
+ return;
+ }
+ Log.d(LOG_TAG, "invokeProgressing mCallId = " + mCallId);
+ mListener.callSessionProgressing(mCallProfile.getMediaProfile());
+ } catch (Throwable t) {
+ Throwable cause = t.getCause();
+ if (t instanceof DeadObjectException
+ || (cause != null && cause instanceof DeadObjectException)) {
+ fail("starting cause Throwable to be thrown: " + t);
+ }
+ }
+ });
+ setState(ImsCallSessionImplBase.State.ESTABLISHING);
+
+ imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+ postAndRunTask(() -> {
+ try {
+ if (mListener == null) {
+ return;
+ }
+ Log.d(LOG_TAG, "invokeStarted mCallId = " + mCallId);
+ mListener.callSessionInitiated(mCallProfile);
+ } catch (Throwable t) {
+ Throwable cause = t.getCause();
+ if (t instanceof DeadObjectException
+ || (cause != null && cause instanceof DeadObjectException)) {
+ fail("starting cause Throwable to be thrown: " + t);
+ }
+ }
+ });
+ setState(ImsCallSessionImplBase.State.ESTABLISHED);
+ }
+
+ void startFailed() {
+ imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_STATE_CHANGE);
+ postAndRunTask(() -> {
+ try {
+ if (mListener == null) {
+ return;
+ }
+ Log.d(LOG_TAG, "invokestartFailed mCallId = " + mCallId);
+ mListener.callSessionInitiatingFailed(getReasonInfo(
+ ImsReasonInfo.CODE_LOCAL_INTERNAL_ERROR, ImsReasonInfo.CODE_UNSPECIFIED));
+ } catch (Throwable t) {
+ Throwable cause = t.getCause();
+ if (t instanceof DeadObjectException
+ || (cause != null && cause instanceof DeadObjectException)) {
+ fail("starting cause Throwable to be thrown: " + t);
+ }
+ }
+ });
+ setState(ImsCallSessionImplBase.State.TERMINATED);
+ }
+
+ @Override
+ public void accept(int callType, ImsStreamMediaProfile profile) {
+ Log.i(LOG_TAG, "Accept Call");
+ postAndRunTask(() -> {
+ try {
+ if (mListener == null) {
+ return;
+ }
+ Log.d(LOG_TAG, "invokeStarted mCallId = " + mCallId);
+ mListener.callSessionInitiated(mCallProfile);
+ } catch (Throwable t) {
+ Throwable cause = t.getCause();
+ if (t instanceof DeadObjectException
+ || (cause != null && cause instanceof DeadObjectException)) {
+ fail("starting cause Throwable to be thrown: " + t);
+ }
+ }
+ });
+ setState(ImsCallSessionImplBase.State.ESTABLISHED);
+ }
+
+ @Override
+ public void reject(int reason) {
+ int state = getState();
+ if (state == ImsCallSessionImplBase.State.ESTABLISHED) {
+ postAndRunTask(() -> {
+ try {
+ if (mListener == null) {
+ return;
+ }
+ Log.d(LOG_TAG, "invokeTerminated mCallId = " + mCallId);
+ mListener.callSessionTerminated(getReasonInfo(
+ ImsReasonInfo.CODE_USER_TERMINATED, ImsReasonInfo.CODE_UNSPECIFIED));
+ } catch (Throwable t) {
+ Throwable cause = t.getCause();
+ if (t instanceof DeadObjectException
+ || (cause != null && cause instanceof DeadObjectException)) {
+ fail("starting cause Throwable to be thrown: " + t);
+ }
+ }
+ });
+ setState(ImsCallSessionImplBase.State.TERMINATED);
+ }
+ }
+
+ @Override
+ public void terminate(int reason) {
+ int state = getState();
+ postAndRunTask(() -> {
+ try {
+ if (mListener == null) {
+ return;
+ }
+ Log.d(LOG_TAG, "invokeTerminated mCallId = " + mCallId);
+ mListener.callSessionTerminated(getReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED,
+ ImsReasonInfo.CODE_UNSPECIFIED));
+ } catch (Throwable t) {
+ Throwable cause = t.getCause();
+ if (t instanceof DeadObjectException
+ || (cause != null && cause instanceof DeadObjectException)) {
+ fail("starting cause Throwable to be thrown: " + t);
+ }
+ }
+ });
+ setState(ImsCallSessionImplBase.State.TERMINATED);
+ }
+
+ // End the Incoming Call from local side after accept.
+ public void terminateIncomingCall() {
+ int state = getState();
+ if (state == ImsCallSessionImplBase.State.ESTABLISHED) {
+ postAndRunTask(() -> {
+ try {
+ if (mListener == null) {
+ return;
+ }
+ Log.d(LOG_TAG, "invokeTerminated mCallId = " + mCallId);
+ mListener.callSessionTerminated(getReasonInfo(
+ ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE,
+ ImsReasonInfo.CODE_UNSPECIFIED));
+ } catch (Throwable t) {
+ Throwable cause = t.getCause();
+ if (t instanceof DeadObjectException
+ || (cause != null && cause instanceof DeadObjectException)) {
+ fail("starting cause Throwable to be thrown: " + t);
+ }
+ }
+ });
+ setState(ImsCallSessionImplBase.State.TERMINATED);
+ }
+ }
+
+ @Override
+ public void hold(ImsStreamMediaProfile profile) {
+ if (isTestType(TEST_TYPE_HOLD_FAILED)) {
+ holdFailed(profile);
+ } else {
+ int audioDirection = profile.getAudioDirection();
+ if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND) {
+ ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
+ ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
+ ImsStreamMediaProfile.DIRECTION_RECEIVE,
+ ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+ ImsStreamMediaProfile.DIRECTION_INVALID,
+ ImsStreamMediaProfile.RTT_MODE_DISABLED);
+ ImsCallProfile mprofile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
+ ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
+ mCallProfile.updateMediaProfile(mprofile);
+ }
+ setState(ImsCallSessionImplBase.State.RENEGOTIATING);
+
+ postAndRunTask(() -> {
+ imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+ try {
+ if (mListener == null) {
+ return;
+ }
+ Log.d(LOG_TAG, "invokeHeld mCallId = " + mCallId);
+ mListener.callSessionHeld(mCallProfile);
+ } catch (Throwable t) {
+ Throwable cause = t.getCause();
+ if (t instanceof DeadObjectException
+ || (cause != null && cause instanceof DeadObjectException)) {
+ fail("starting cause Throwable to be thrown: " + t);
+ }
+ }
+ });
+ setState(ImsCallSessionImplBase.State.ESTABLISHED);
+ }
+ }
+
+ @Override
+ public void resume(ImsStreamMediaProfile profile) {
+ if (isTestType(TEST_TYPE_RESUME_FAILED)) {
+ resumeFailed(profile);
+ } else {
+ int audioDirection = profile.getAudioDirection();
+ if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE) {
+ ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
+ ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
+ ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE,
+ ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+ ImsStreamMediaProfile.DIRECTION_INVALID,
+ ImsStreamMediaProfile.RTT_MODE_DISABLED);
+ ImsCallProfile mprofile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
+ ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
+ mCallProfile.updateMediaProfile(mprofile);
+ }
+ setState(ImsCallSessionImplBase.State.RENEGOTIATING);
+
+ postAndRunTask(() -> {
+ imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+ try {
+ if (mListener == null) {
+ return;
+ }
+ Log.d(LOG_TAG, "invokeResume mCallId = " + mCallId);
+ mListener.callSessionResumed(mCallProfile);
+ } catch (Throwable t) {
+ Throwable cause = t.getCause();
+ if (t instanceof DeadObjectException
+ || (cause != null && cause instanceof DeadObjectException)) {
+ fail("starting cause Throwable to be thrown: " + t);
+ }
+ }
+ });
+ setState(ImsCallSessionImplBase.State.ESTABLISHED);
+ }
+ }
+
+ private void holdFailed(ImsStreamMediaProfile profile) {
+ int audioDirection = profile.getAudioDirection();
+ if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND) {
+ postAndRunTask(() -> {
+ imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+ try {
+ if (mListener == null) {
+ return;
+ }
+ Log.d(LOG_TAG, "invokeHoldFailed mCallId = " + mCallId);
+ mListener.callSessionHoldFailed(getReasonInfo(ImsReasonInfo
+ .CODE_SESSION_MODIFICATION_FAILED, ImsReasonInfo.CODE_UNSPECIFIED));
+ } catch (Throwable t) {
+ Throwable cause = t.getCause();
+ if (t instanceof DeadObjectException
+ || (cause != null && cause instanceof DeadObjectException)) {
+ fail("starting cause Throwable to be thrown: " + t);
+ }
+ }
+ });
+ setState(ImsCallSessionImplBase.State.ESTABLISHED);
+ }
+ }
+
+ private void resumeFailed(ImsStreamMediaProfile profile) {
+ int audioDirection = profile.getAudioDirection();
+ if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE) {
+ postAndRunTask(() -> {
+ imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+ try {
+ if (mListener == null) {
+ return;
+ }
+ Log.d(LOG_TAG, "invokeResumeFailed mCallId = " + mCallId);
+ mListener.callSessionResumeFailed(getReasonInfo(ImsReasonInfo
+ .CODE_SESSION_MODIFICATION_FAILED, ImsReasonInfo.CODE_UNSPECIFIED));
+ } catch (Throwable t) {
+ Throwable cause = t.getCause();
+ if (t instanceof DeadObjectException
+ || (cause != null && cause instanceof DeadObjectException)) {
+ fail("starting cause Throwable to be thrown: " + t);
+ }
+ }
+ });
+ setState(ImsCallSessionImplBase.State.ESTABLISHED);
+ }
+ }
+
+ private void setState(int state) {
+ if (mState != state) {
+ Log.d(LOG_TAG, "ImsCallSession :: " + mState + " >> " + state);
+ mState = state;
+ }
+ }
+
+ public boolean isInTerminated() {
+ return (mState == ImsCallSessionImplBase.State.TERMINATED) ? true : false;
+ }
+
+ private ImsReasonInfo getReasonInfo(int code, int extraCode) {
+ ImsReasonInfo reasonInfo = new ImsReasonInfo(code, extraCode, "");
+ return reasonInfo;
+ }
+
+ public void addTestType(int type) {
+ mTestType |= type;
+ }
+
+ public void removeTestType(int type) {
+ mTestType &= ~type;
+ }
+
+ public boolean isTestType(int type) {
+ return ((mTestType & type) == type);
+ }
+
+ public Executor getExecutor() {
+ return mCallBackExecutor;
+ }
+
+ private void postAndRunTask(Runnable task) {
+ mCallBackExecutor.execute(task);
+ }
+
+ private static Looper createLooper(String name) {
+ HandlerThread thread = new HandlerThread(name);
+ thread.start();
+
+ Looper looper = thread.getLooper();
+
+ if (looper == null) {
+ return Looper.getMainLooper();
+ }
+ return looper;
+ }
+ /**
+ * Executes the tasks in the other thread rather than the calling thread.
+ */
+ public class MessageExecutor extends Handler implements Executor {
+ public MessageExecutor(String name) {
+ super(createLooper(name));
+ }
+
+ @Override
+ public void execute(Runnable r) {
+ Message m = Message.obtain(this, 0 /* don't care */, r);
+ m.sendToTarget();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.obj instanceof Runnable) {
+ executeInternal((Runnable) msg.obj);
+ } else {
+ Log.d(LOG_TAG, "[MessageExecutor] handleMessage :: "
+ + "Not runnable object; ignore the msg=" + msg);
+ }
+ }
+
+ private void executeInternal(Runnable r) {
+ try {
+ r.run();
+ } catch (Throwable t) {
+ Log.d(LOG_TAG, "[MessageExecutor] executeInternal :: run task=" + r);
+ t.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java
index 10a39d3..3e3b331 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java
@@ -18,15 +18,25 @@
import android.telephony.ims.RcsClientConfiguration;
import android.telephony.ims.stub.ImsConfigImplBase;
+import android.util.Log;
import java.util.HashMap;
+import java.util.concurrent.Executor;
public class TestImsConfig extends ImsConfigImplBase {
+ private static final String TAG = "TestImsConfig";
private HashMap<Integer, Integer> mIntHashMap = new HashMap<>();
private HashMap<Integer, String> mStringHashMap = new HashMap<>();
TestImsConfig() {
+ Log.d(TAG, "TestImsConfig with default constructor");
+ TestAcsClient.getInstance().setImsConfigImpl(this);
+ }
+
+ TestImsConfig(Executor executor) {
+ super(executor);
+ Log.d(TAG, "TestImsConfig with Executor constructor");
TestAcsClient.getInstance().setImsConfigImpl(this);
}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java
index afdd3d1..cb3a4d9 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java
@@ -17,13 +17,26 @@
package android.telephony.ims.cts;
import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.Log;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class TestImsRegistration extends ImsRegistrationImplBase {
+ private static final String TAG = "TestImsRegistration";
+
+ public TestImsRegistration() {
+ Log.d(TAG, "TestImsRegistration with default constructor");
+ }
+
+ public TestImsRegistration(Executor executor) {
+ super(executor);
+ Log.d(TAG, "TestImsRegistration with Executor constructor");
+ }
+
public static class NetworkRegistrationInfo {
public final int sipCode;
public final String sipReason;
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
index a012513..97fa8f2 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
@@ -20,7 +20,11 @@
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.telephony.ims.ImsService;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.feature.RcsFeature;
@@ -30,10 +34,12 @@
import android.telephony.ims.stub.SipTransportImplBase;
import android.util.Log;
-
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.util.HashSet;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
@@ -41,22 +47,27 @@
*/
public class TestImsService extends Service {
- private static final String TAG = "GtsImsTestImsService";
+ private static final String TAG = "CtsImsTestImsService";
+ private static MessageExecutor sMessageExecutor = null;
- private static final TestImsRegistration sImsRegistrationImplBase =
- new TestImsRegistration();
-
+ private TestImsRegistration mImsRegistrationImplBase;
private TestRcsFeature mTestRcsFeature;
private TestMmTelFeature mTestMmTelFeature;
private TestImsConfig mTestImsConfig;
private TestSipTransport mTestSipTransport;
private ImsService mTestImsService;
+ private ImsService mTestImsServiceCompat;
+ private Executor mExecutor = Runnable::run;
private boolean mIsEnabled = false;
private boolean mSetNullRcsBinding = false;
private boolean mIsSipTransportImplemented = false;
+ private boolean mIsTestTypeExecutor = false;
+ private boolean mIsImsServiceCompat = false;
private long mCapabilities = 0;
private ImsFeatureConfiguration mFeatureConfig;
- private final Object mLock = new Object();
+ protected boolean mIsTelephonyBound = false;
+ private HashSet<Integer> mSubIDs = new HashSet<Integer>();
+ protected final Object mLock = new Object();
public static final int LATCH_FEATURES_READY = 0;
public static final int LATCH_ENABLE_IMS = 1;
@@ -71,7 +82,8 @@
public static final int LATCH_RCS_CAP_SET = 10;
public static final int LATCH_UCE_LISTENER_SET = 11;
public static final int LATCH_UCE_REQUEST_PUBLISH = 12;
- private static final int LATCH_MAX = 13;
+ public static final int LATCH_ON_UNBIND = 13;
+ private static final int LATCH_MAX = 14;
protected static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
static {
for (int i = 0; i < LATCH_MAX; i++) {
@@ -106,9 +118,193 @@
if (getBaseContext() == null) {
attachBaseContext(context);
}
- mTestImsConfig = new TestImsConfig();
- // For testing, just run on binder thread until required otherwise.
- mTestSipTransport = new TestSipTransport(Runnable::run);
+
+ if (mIsTestTypeExecutor) {
+ mImsRegistrationImplBase = new TestImsRegistration(mExecutor);
+ mTestSipTransport = new TestSipTransport(mExecutor);
+ mTestImsConfig = new TestImsConfig(mExecutor);
+ } else {
+ mImsRegistrationImplBase = new TestImsRegistration();
+ mTestImsConfig = new TestImsConfig();
+ mTestSipTransport = new TestSipTransport();
+ }
+ }
+
+ @Override
+ public ImsFeatureConfiguration querySupportedImsFeatures() {
+ return getFeatureConfig();
+ }
+
+ @Override
+ public long getImsServiceCapabilities() {
+ return mCapabilities;
+ }
+
+ @Override
+ public void readyForFeatureCreation() {
+ synchronized (mLock) {
+ countDownLatch(LATCH_FEATURES_READY);
+ }
+ }
+
+ @Override
+ public void enableImsForSubscription(int slotId, int subId) {
+ synchronized (mLock) {
+ countDownLatch(LATCH_ENABLE_IMS);
+ mSubIDs.add(subId);
+ setIsEnabled(true);
+ }
+ }
+
+ @Override
+ public void disableImsForSubscription(int slotId, int subId) {
+ synchronized (mLock) {
+ countDownLatch(LATCH_DISABLE_IMS);
+ mSubIDs.add(subId);
+ setIsEnabled(false);
+ }
+ }
+
+ @Override
+ public RcsFeature createRcsFeatureForSubscription(int slotId, int subId) {
+ TestImsService.ReadyListener readyListener = () -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_RCS_READY);
+ }
+ };
+
+ TestImsService.RemovedListener removedListener = () -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_REMOVE_RCS);
+ mTestRcsFeature = null;
+ }
+ };
+
+ TestImsService.CapabilitiesSetListener setListener = () -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_RCS_CAP_SET);
+ }
+ };
+
+ TestImsService.RcsCapabilityExchangeEventListener capExchangeEventListener = () -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_UCE_LISTENER_SET);
+ }
+ };
+
+ synchronized (mLock) {
+ countDownLatch(LATCH_CREATE_RCS);
+ mSubIDs.add(subId);
+
+ if (mIsTestTypeExecutor) {
+ mTestRcsFeature = new TestRcsFeature(readyListener, removedListener,
+ setListener, capExchangeEventListener, mExecutor);
+ } else {
+ mTestRcsFeature = new TestRcsFeature(readyListener, removedListener,
+ setListener, capExchangeEventListener);
+ }
+
+ // Setup UCE request listener
+ mTestRcsFeature.setDeviceCapPublishListener(() -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_UCE_REQUEST_PUBLISH);
+ }
+ });
+
+ if (mSetNullRcsBinding) {
+ return null;
+ }
+ return mTestRcsFeature;
+ }
+ }
+
+ @Override
+ public ImsConfigImplBase getConfigForSubscription(int slotId, int subId) {
+ mSubIDs.add(subId);
+ return mTestImsConfig;
+ }
+
+ @Override
+ public MmTelFeature createMmTelFeatureForSubscription(int slotId, int subId) {
+ TestImsService.ReadyListener readyListener = () -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_MMTEL_READY);
+ }
+ };
+
+ TestImsService.RemovedListener removedListener = () -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_REMOVE_MMTEL);
+ mTestMmTelFeature = null;
+ }
+ };
+
+ TestImsService.CapabilitiesSetListener capSetListener = () -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_MMTEL_CAP_SET);
+ }
+ };
+
+ synchronized (mLock) {
+ countDownLatch(LATCH_CREATE_MMTEL);
+ mSubIDs.add(subId);
+ if (mIsTestTypeExecutor) {
+ mTestMmTelFeature = new TestMmTelFeature(readyListener, removedListener,
+ capSetListener, mExecutor);
+ } else {
+ mTestMmTelFeature = new TestMmTelFeature(readyListener, removedListener,
+ capSetListener);
+ }
+
+ return mTestMmTelFeature;
+ }
+ }
+
+ @Override
+ public ImsRegistrationImplBase getRegistrationForSubscription(int slotId, int subId) {
+ mSubIDs.add(subId);
+ return mImsRegistrationImplBase;
+ }
+
+ @Nullable
+ @Override
+ public SipTransportImplBase getSipTransport(int slotId) {
+ if (mIsSipTransportImplemented) {
+ return mTestSipTransport;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public @NonNull Executor getExecutor() {
+ if (mIsTestTypeExecutor) {
+ return mExecutor;
+ } else {
+ mExecutor = Runnable::run;
+ return mExecutor;
+ }
+ }
+ }
+
+ private class ImsServiceUT_compat extends ImsService {
+
+ ImsServiceUT_compat(Context context) {
+ // As explained above, ImsServiceUT is created in order to get around classloader
+ // restrictions. Attach the base context from the wrapper ImsService.
+ if (getBaseContext() == null) {
+ attachBaseContext(context);
+ }
+
+ if (mIsTestTypeExecutor) {
+ mImsRegistrationImplBase = new TestImsRegistration(mExecutor);
+ mTestSipTransport = new TestSipTransport(mExecutor);
+ mTestImsConfig = new TestImsConfig(mExecutor);
+ } else {
+ mImsRegistrationImplBase = new TestImsRegistration();
+ mTestImsConfig = new TestImsConfig();
+ mTestSipTransport = new TestSipTransport();
+ }
}
@Override
@@ -146,33 +342,42 @@
@Override
public RcsFeature createRcsFeature(int slotId) {
+
+ TestImsService.ReadyListener readyListener = () -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_RCS_READY);
+ }
+ };
+
+ TestImsService.RemovedListener removedListener = () -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_REMOVE_RCS);
+ mTestRcsFeature = null;
+ }
+ };
+
+ TestImsService.CapabilitiesSetListener setListener = () -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_RCS_CAP_SET);
+ }
+ };
+
+ TestImsService.RcsCapabilityExchangeEventListener capExchangeEventListener = () -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_UCE_LISTENER_SET);
+ }
+ };
+
synchronized (mLock) {
countDownLatch(LATCH_CREATE_RCS);
- mTestRcsFeature = new TestRcsFeature(getBaseContext(),
- //onReady
- () -> {
- synchronized (mLock) {
- countDownLatch(LATCH_RCS_READY);
- }
- },
- //onRemoved
- () -> {
- synchronized (mLock) {
- countDownLatch(LATCH_REMOVE_RCS);
- mTestRcsFeature = null;
- }
- },
- //onCapabilitiesSet
- () -> {
- synchronized (mLock) {
- countDownLatch(LATCH_RCS_CAP_SET);
- }
- },
- () -> {
- synchronized (mLock) {
- countDownLatch(LATCH_UCE_LISTENER_SET);
- }
- });
+
+ if (mIsTestTypeExecutor) {
+ mTestRcsFeature = new TestRcsFeature(readyListener, removedListener,
+ setListener, capExchangeEventListener, mExecutor);
+ } else {
+ mTestRcsFeature = new TestRcsFeature(readyListener, removedListener,
+ setListener, capExchangeEventListener);
+ }
// Setup UCE request listener
mTestRcsFeature.setDeviceCapPublishListener(() -> {
@@ -195,36 +400,42 @@
@Override
public MmTelFeature createMmTelFeature(int slotId) {
+ TestImsService.ReadyListener readyListener = () -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_MMTEL_READY);
+ }
+ };
+
+ TestImsService.RemovedListener removedListener = () -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_REMOVE_MMTEL);
+ mTestMmTelFeature = null;
+ }
+ };
+
+ TestImsService.CapabilitiesSetListener capSetListener = () -> {
+ synchronized (mLock) {
+ countDownLatch(LATCH_MMTEL_CAP_SET);
+ }
+ };
+
synchronized (mLock) {
countDownLatch(LATCH_CREATE_MMTEL);
- mTestMmTelFeature = new TestMmTelFeature(
- //onReady
- () -> {
- synchronized (mLock) {
- countDownLatch(LATCH_MMTEL_READY);
- }
- },
- //onRemoved
- () -> {
- synchronized (mLock) {
- countDownLatch(LATCH_REMOVE_MMTEL);
- mTestMmTelFeature = null;
- }
- },
- //onCapabilitiesSet
- () -> {
- synchronized (mLock) {
- countDownLatch(LATCH_MMTEL_CAP_SET);
- }
- }
- );
+ if (mIsTestTypeExecutor) {
+ mTestMmTelFeature = new TestMmTelFeature(readyListener, removedListener,
+ capSetListener, mExecutor);
+ } else {
+ mTestMmTelFeature = new TestMmTelFeature(readyListener, removedListener,
+ capSetListener);
+ }
+
return mTestMmTelFeature;
}
}
@Override
public ImsRegistrationImplBase getRegistration(int slotId) {
- return sImsRegistrationImplBase;
+ return mImsRegistrationImplBase;
}
@Nullable
@@ -236,6 +447,62 @@
return null;
}
}
+
+ @Override
+ public @NonNull Executor getExecutor() {
+ if (mIsTestTypeExecutor) {
+ return mExecutor;
+ } else {
+ mExecutor = Runnable::run;
+ return mExecutor;
+ }
+ }
+ }
+
+ private static Looper createLooper(String name) {
+ HandlerThread thread = new HandlerThread(name);
+ thread.start();
+
+ Looper looper = thread.getLooper();
+
+ if (looper == null) {
+ return Looper.getMainLooper();
+ }
+ return looper;
+ }
+
+ /**
+ * Executes the tasks in the other thread rather than the calling thread.
+ */
+ public class MessageExecutor extends Handler implements Executor {
+ public MessageExecutor(String name) {
+ super(createLooper(name));
+ }
+
+ @Override
+ public void execute(Runnable r) {
+ Message m = Message.obtain(this, 0, r);
+ m.sendToTarget();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.obj instanceof Runnable) {
+ executeInternal((Runnable) msg.obj);
+ } else {
+ Log.d(TAG, "[MessageExecutor] handleMessage :: "
+ + "Not runnable object; ignore the msg=" + msg);
+ }
+ }
+
+ private void executeInternal(Runnable r) {
+ try {
+ r.run();
+ } catch (Throwable t) {
+ Log.d(TAG, "[MessageExecutor] executeInternal :: run task=" + r);
+ t.printStackTrace();
+ }
+ }
}
private final LocalBinder mBinder = new LocalBinder();
@@ -256,18 +523,53 @@
}
}
+ protected ImsService getImsServiceCompat() {
+ synchronized (mLock) {
+ if (mTestImsServiceCompat != null) {
+ return mTestImsServiceCompat;
+ }
+ mTestImsServiceCompat = new ImsServiceUT_compat(this);
+ return mTestImsServiceCompat;
+ }
+ }
+
@Override
public IBinder onBind(Intent intent) {
- if ("android.telephony.ims.ImsService".equals(intent.getAction())) {
- if (ImsUtils.VDBG) {
- Log.d(TAG, "onBind-Remote");
+ synchronized (mLock) {
+ if ("android.telephony.ims.ImsService".equals(intent.getAction())) {
+ mIsTelephonyBound = true;
+ if (mIsImsServiceCompat) {
+ if (ImsUtils.VDBG) {
+ Log.d(TAG, "onBind-Remote-Compat");
+ }
+ return getImsServiceCompat().onBind(intent);
+ } else {
+ if (ImsUtils.VDBG) {
+ Log.d(TAG, "onBind-Remote");
+ }
+ return getImsService().onBind(intent);
+ }
}
- return getImsService().onBind(intent);
+ if (ImsUtils.VDBG) {
+ Log.i(TAG, "onBind-Local");
+ }
+ return mBinder;
}
- if (ImsUtils.VDBG) {
- Log.i(TAG, "onBind-Local");
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ synchronized (mLock) {
+ if ("android.telephony.ims.ImsService".equals(intent.getAction())) {
+ if (ImsUtils.VDBG) Log.i(TAG, "onUnbind-Remote");
+ mIsTelephonyBound = false;
+ countDownLatch(LATCH_ON_UNBIND);
+ } else {
+ if (ImsUtils.VDBG) Log.i(TAG, "onUnbind-Local");
+ }
+ // return false so that onBind is called next time.
+ return false;
}
- return mBinder;
}
public void resetState() {
@@ -277,10 +579,38 @@
mIsEnabled = false;
mSetNullRcsBinding = false;
mIsSipTransportImplemented = false;
+ mIsTestTypeExecutor = false;
+ mIsImsServiceCompat = false;
mCapabilities = 0;
for (int i = 0; i < LATCH_MAX; i++) {
sLatches[i] = new CountDownLatch(1);
}
+
+ if (sMessageExecutor != null) {
+ sMessageExecutor.getLooper().quit();
+ sMessageExecutor = null;
+ }
+ mSubIDs.clear();
+ }
+ }
+
+ public boolean isTelephonyBound() {
+ return mIsTelephonyBound;
+ }
+
+ public void setExecutorTestType(boolean type) {
+ mIsTestTypeExecutor = type;
+ if (mIsTestTypeExecutor) {
+ if (sMessageExecutor == null) {
+ sMessageExecutor = new MessageExecutor("TestImsService");
+ }
+ mExecutor = sMessageExecutor;
+ }
+ }
+
+ public void setImsServiceCompat() {
+ synchronized (mLock) {
+ mIsImsServiceCompat = true;
}
}
@@ -329,20 +659,7 @@
}
public boolean waitForLatchCountdown(int latchIndex) {
- boolean complete = false;
- try {
- CountDownLatch latch;
- synchronized (mLock) {
- latch = sLatches[latchIndex];
- }
- complete = latch.await(ImsUtils.TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- // complete == false
- }
- synchronized (mLock) {
- sLatches[latchIndex] = new CountDownLatch(1);
- }
- return complete;
+ return waitForLatchCountdown(latchIndex, ImsUtils.TEST_TIMEOUT_MS);
}
public boolean waitForLatchCountdown(int latchIndex, long waitMs) {
@@ -352,7 +669,12 @@
synchronized (mLock) {
latch = sLatches[latchIndex];
}
+ long startTime = System.currentTimeMillis();
complete = latch.await(waitMs, TimeUnit.MILLISECONDS);
+ if (ImsUtils.VDBG) {
+ Log.i(TAG, "Latch " + latchIndex + " took "
+ + (System.currentTimeMillis() - startTime) + " ms to count down.");
+ }
} catch (InterruptedException e) {
// complete == false
}
@@ -388,11 +710,15 @@
public TestImsRegistration getImsRegistration() {
synchronized (mLock) {
- return sImsRegistrationImplBase;
+ return mImsRegistrationImplBase;
}
}
public ImsConfigImplBase getConfig() {
return mTestImsConfig;
}
+
+ public HashSet<Integer> getSubIDs() {
+ return mSubIDs;
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestMmTelFeature.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestMmTelFeature.java
index cb82d71..d300b71 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestMmTelFeature.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestMmTelFeature.java
@@ -16,15 +16,20 @@
package android.telephony.ims.cts;
+import android.os.Bundle;
+import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsStreamMediaProfile;
import android.telephony.ims.RtpHeaderExtensionType;
import android.telephony.ims.feature.CapabilityChangeRequest;
import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.stub.ImsCallSessionImplBase;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
public class TestMmTelFeature extends MmTelFeature {
@@ -39,10 +44,26 @@
private TestImsSmsImpl mSmsImpl;
private Set<RtpHeaderExtensionType> mOfferedRtpHeaderExtensionTypes;
private CountDownLatch mOfferedRtpHeaderExtensionLatch = new CountDownLatch(1);
+ private TestImsCallSessionImpl mCallSession;
TestMmTelFeature(TestImsService.ReadyListener readyListener,
TestImsService.RemovedListener removedListener,
TestImsService.CapabilitiesSetListener setListener) {
+ Log.d(TAG, "TestMmTelFeature with default constructor");
+ mReadyListener = readyListener;
+ mRemovedListener = removedListener;
+ mCapSetListener = setListener;
+ mSmsImpl = new TestImsSmsImpl();
+ // Must set the state to READY in the constructor - onFeatureReady depends on the state
+ // being ready.
+ setFeatureState(STATE_READY);
+ }
+
+ TestMmTelFeature(TestImsService.ReadyListener readyListener,
+ TestImsService.RemovedListener removedListener,
+ TestImsService.CapabilitiesSetListener setListener, Executor executor) {
+ super(executor);
+ Log.d(TAG, "TestMmTelFeature with Executor constructor");
mReadyListener = readyListener;
mRemovedListener = removedListener;
mCapSetListener = setListener;
@@ -104,6 +125,26 @@
mOfferedRtpHeaderExtensionLatch.countDown();
}
+ @Override
+ public ImsCallProfile createCallProfile(int serviceType, int callType) {
+ ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
+ ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
+ ImsStreamMediaProfile.DIRECTION_INVALID,
+ ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+ ImsStreamMediaProfile.DIRECTION_INVALID,
+ ImsStreamMediaProfile.RTT_MODE_DISABLED);
+ ImsCallProfile profile = new ImsCallProfile(serviceType, callType,
+ new Bundle(), mediaProfile);
+ return profile;
+ }
+
+ @Override
+ public ImsCallSessionImplBase createCallSession(ImsCallProfile profile) {
+ ImsCallSessionImplBase s = new TestImsCallSessionImpl(profile);
+ mCallSession = (TestImsCallSessionImpl) s;
+ return s != null ? s : null;
+ }
+
public void setCapabilities(MmTelCapabilities capabilities) {
mCapabilities = capabilities;
}
@@ -119,4 +160,34 @@
public CountDownLatch getOfferedRtpHeaderExtensionLatch() {
return mOfferedRtpHeaderExtensionLatch;
}
+
+ public TestImsCallSessionImpl getImsCallsession() {
+ return mCallSession;
+ }
+
+ public boolean isCallSessionCreated() {
+ return (mCallSession != null);
+ }
+
+ public void onIncomingCallReceived(Bundle extras) {
+ Log.d(TAG, "onIncomingCallReceived");
+
+ ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
+ ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
+ ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE,
+ ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+ ImsStreamMediaProfile.DIRECTION_INVALID,
+ ImsStreamMediaProfile.RTT_MODE_DISABLED);
+
+ ImsCallProfile callProfile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
+ ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
+
+ TestImsCallSessionImpl incomingSession = new TestImsCallSessionImpl(callProfile);
+ mCallSession = incomingSession;
+
+ Executor executor = incomingSession.getExecutor();
+ executor.execute(() -> {
+ notifyIncomingCall(incomingSession, extras);
+ });
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
index 08eaa85..3915fc5 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
@@ -16,7 +16,6 @@
package android.telephony.ims.cts;
-import android.content.Context;
import android.telephony.ims.feature.CapabilityChangeRequest;
import android.telephony.ims.feature.RcsFeature;
import android.telephony.ims.stub.CapabilityExchangeEventListener;
@@ -43,13 +42,30 @@
private CapabilityExchangeEventListener mCapEventListener;
private TestImsService.DeviceCapPublishListener mDeviceCapPublishListener;
- TestRcsFeature(Context context,
- TestImsService.ReadyListener readyListener,
+ TestRcsFeature(TestImsService.ReadyListener readyListener,
TestImsService.RemovedListener removedListener,
TestImsService.CapabilitiesSetListener setListener,
TestImsService.RcsCapabilityExchangeEventListener capExchangeEventListener) {
- super(context.getMainExecutor());
+ super();
+ Log.d(TAG, "TestRcsFeature with default constructor");
+ mReadyListener = readyListener;
+ mRemovedListener = removedListener;
+ mCapExchangeEventListener = capExchangeEventListener;
+ mRcsCapabilityChangedListener = setListener;
+ mRcsCapabilitiesLte = new RcsImsCapabilities(RcsImsCapabilities.CAPABILITY_TYPE_NONE);
+ mRcsCapabilitiesIWan = new RcsImsCapabilities(RcsImsCapabilities.CAPABILITY_TYPE_NONE);
+
+ setFeatureState(STATE_READY);
+ }
+
+ TestRcsFeature(TestImsService.ReadyListener readyListener,
+ TestImsService.RemovedListener removedListener,
+ TestImsService.CapabilitiesSetListener setListener,
+ TestImsService.RcsCapabilityExchangeEventListener capExchangeEventListener,
+ Executor executor) {
+ super(executor);
+ Log.d(TAG, "TestRcsFeature with Executor constructor");
mReadyListener = readyListener;
mRemovedListener = removedListener;
mCapExchangeEventListener = capExchangeEventListener;
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipTransport.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipTransport.java
index 0ea195c..88dcc2a 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipTransport.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipTransport.java
@@ -23,6 +23,7 @@
import android.telephony.ims.DelegateStateCallback;
import android.telephony.ims.stub.SipDelegate;
import android.telephony.ims.stub.SipTransportImplBase;
+import android.util.Log;
import androidx.annotation.NonNull;
@@ -34,6 +35,7 @@
public class TestSipTransport extends SipTransportImplBase {
+ private static final String TAG = "TestSipTransport";
public static final String ONE_TO_ONE_CHAT_TAG =
"+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gppservice.ims.icsi.oma.cpm.msg\"";
public static final String GROUP_CHAT_TAG =
@@ -54,8 +56,13 @@
private final ArrayList<TestSipDelegate> mDelegates = new ArrayList<>();
private final Object mLock = new Object();
+ public TestSipTransport() {
+ Log.d(TAG, "TestSipTransport with default constructor");
+ }
+
public TestSipTransport(Executor executor) {
super(executor);
+ Log.d(TAG, "TestSipTransport with Executor constructor");
}
@Override
diff --git a/tests/tests/telephony/sdk28/Android.bp b/tests/tests/telephony/sdk28/Android.bp
deleted file mode 100644
index bf23f39..0000000
--- a/tests/tests/telephony/sdk28/Android.bp
+++ /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.
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test {
- name: "CtsTelephonySdk28TestCases",
- defaults: ["cts_defaults"],
- static_libs: [
- "ctstestrunner-axt",
- "compatibility-device-util-axt",
- ],
- srcs: ["src/**/*.java"],
- // TODO: what can be done here? it used to be
- // sdk_version: "28", but this is not compatible with compatibility-device-util-axt
- sdk_version: "test_current",
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
- ],
- libs: [
- "android.test.runner",
- "android.test.base",
- ],
-}
diff --git a/tests/tests/telephony/sdk28/AndroidManifest.xml b/tests/tests/telephony/sdk28/AndroidManifest.xml
deleted file mode 100644
index 7955df5..0000000
--- a/tests/tests/telephony/sdk28/AndroidManifest.xml
+++ /dev/null
@@ -1,37 +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"
- package="android.telephony.sdk28.cts">
- <uses-sdk android:targetSdkVersion="28"
- android:minSdkVersion="28"
- android:maxSdkVersion="28" />
-
- <uses-permission android:name="android.permission.READ_PHONE_STATE" />
- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.telephony.sdk28.cts">
- <meta-data android:name="listener"
- android:value="com.android.cts.runner.CtsTestRunListener" />
- </instrumentation>
-
-</manifest>
-
diff --git a/tests/tests/telephony/sdk28/AndroidTest.xml b/tests/tests/telephony/sdk28/AndroidTest.xml
deleted file mode 100644
index b165ade..0000000
--- a/tests/tests/telephony/sdk28/AndroidTest.xml
+++ /dev/null
@@ -1,41 +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 Telephony test cases">
- <option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="telecom" />
- <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
- <option name="not-shardable" value="true" />
- <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
- <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
- <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
- <target_preparer class="android.telephony.cts.preconditions.TelephonyPreparer">
- <option name="apk" value="CtsTelephonyPreparerApp.apk" />
- <option name="package" value="android.telephony.cts.preconditions.preparerApp" />
- </target_preparer>
- <target_preparer class="android.telephony.cts.preconditions.TelephonyCleaner">
- <option name="apk" value="CtsTelephonyCleanerApp.apk" />
- <option name="package" value="android.telephony.cts.preconditions.cleanerApp" />
- </target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="CtsTelephonySdk28TestCases.apk" />
- </target_preparer>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="android.telephony.sdk28.cts" />
- <option name="hidden-api-checks" value="false"/>
- </test>
-</configuration>
diff --git a/tests/tests/telephony/sdk28/TEST_MAPPING b/tests/tests/telephony/sdk28/TEST_MAPPING
deleted file mode 100644
index 141ee9e..0000000
--- a/tests/tests/telephony/sdk28/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "presubmit": [
- {
- "name": "CtsTelephonySdk28TestCases"
- }
- ]
-}
diff --git a/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/CellInfoTest.java b/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/CellInfoTest.java
deleted file mode 100644
index 09c890a..0000000
--- a/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/CellInfoTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony.sdk28.cts;
-
-import static androidx.test.InstrumentationRegistry.getContext;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.telephony.AccessNetworkConstants;
-import android.telephony.CellInfo;
-import android.telephony.NetworkRegistrationInfo;
-import android.telephony.ServiceState;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-
-import com.android.compatibility.common.util.ShellIdentityUtils;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.List;
-
-public class CellInfoTest {
- private static final String TAG = "CellInfoTest";
-
- private static final int MAX_WAIT_SECONDS = 15;
- private static final int POLL_INTERVAL_MILLIS = 1000;
-
- private static final String[] sPermissions = new String[] {
- android.Manifest.permission.READ_PHONE_STATE,
- android.Manifest.permission.ACCESS_COARSE_LOCATION};
-
- private TelephonyManager mTm;
- private PackageManager mPm;
-
- private boolean isCamped() {
- ServiceState ss = ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTm, TelephonyManager::getServiceState);
-
- if (ss == null) return false;
- if (ss.getState() == ServiceState.STATE_EMERGENCY_ONLY) return true;
- List<NetworkRegistrationInfo> nris = ss.getNetworkRegistrationInfoList();
- for (NetworkRegistrationInfo nri : nris) {
- if (nri.getTransportType() != AccessNetworkConstants.TRANSPORT_TYPE_WWAN) continue;
- if (nri.isRegistered()) return true;
- }
- return false;
- }
-
- @Before
- public void setUp() throws Exception {
- mTm = (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
- mPm = getContext().getPackageManager();
-
- for (String permission : sPermissions) {
- assertTrue("Something (not this test) has denied needed permission=" + permission,
- getContext().checkSelfPermission(permission)
- == android.content.pm.PackageManager.PERMISSION_GRANTED);
- }
- }
-
- @Test
- public void testCellInfoSdk28() {
- if (!mPm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
- return;
- }
-
- if (!isCamped()) fail("Device is not camped to a cell");
-
- List<CellInfo> cellInfo = ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTm, TelephonyManager::getAllCellInfo);
-
- // getAllCellInfo should never return null, and there should be at least one entry.
- assertNotNull("TelephonyManager.getAllCellInfo() returned NULL CellInfo", cellInfo);
- assertFalse("TelephonyManager.getAllCellInfo() returned an empty list", cellInfo.isEmpty());
-
- final long initialTime = cellInfo.get(0).getTimeStamp();
-
- for(int i = 0; i < MAX_WAIT_SECONDS; i++) {
- try {
- Thread.sleep(POLL_INTERVAL_MILLIS); // 1 second
- } catch (InterruptedException ie) {
- fail("Thread was interrupted");
- }
- List<CellInfo> newCellInfo = ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTm, TelephonyManager::getAllCellInfo);
- assertNotNull("TelephonyManager.getAllCellInfo() returned NULL CellInfo", newCellInfo);
- assertFalse("TelephonyManager.getAllCellInfo() returned an empty list",
- newCellInfo.isEmpty());
- // Test that new CellInfo has been retrieved from the modem
- if (newCellInfo.get(0).getTimeStamp() != initialTime) return;
- }
- fail("CellInfo failed to update after " + MAX_WAIT_SECONDS + " seconds.");
- }
-}
diff --git a/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/PhoneStateListenerTest.java b/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/PhoneStateListenerTest.java
deleted file mode 100644
index c890a2a..0000000
--- a/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/PhoneStateListenerTest.java
+++ /dev/null
@@ -1,137 +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.
- */
-package android.telephony.sdk28.cts;
-
-import static androidx.test.InstrumentationRegistry.getContext;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Looper;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-
-import androidx.test.filters.FlakyTest;
-
-import com.android.compatibility.common.util.TestThread;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-@FlakyTest
-public class PhoneStateListenerTest {
-
- public static final long WAIT_TIME = 1000;
- private boolean mOnServiceStateChangedCalled;
- private TelephonyManager mTelephonyManager;
- private PhoneStateListener mListener;
- private final Object mLock = new Object();
- private boolean mHasTelephony = true;
- private static final String TAG = "android.telephony.cts.PhoneStateListenerTest";
-
- @Before
- public void setUp() throws Exception {
- mTelephonyManager =
- (TelephonyManager)getContext().getSystemService(Context.TELEPHONY_SERVICE);
-
- PackageManager packageManager = getContext().getPackageManager();
- if (packageManager != null) {
- if (!packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "Some tests requiring FEATURE_TELEPHONY will be skipped");
- mHasTelephony = false;
- }
- }
- }
-
- @After
- public void tearDown() throws Exception {
- if (mListener != null) {
- // unregister the listener
- mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_NONE);
- }
- }
-
- @Test
- public void testPhoneStateListener() {
- Looper.prepare();
- new PhoneStateListener();
- }
-
- @Test
- public void testOnUnRegisterFollowedByRegister() throws Throwable {
- if (!mHasTelephony) {
- return;
- }
-
- TestThread t = new TestThread(new Runnable() {
- public void run() {
- Looper.prepare();
-
- mListener = new PhoneStateListener() {
- @Override
- public void onServiceStateChanged(ServiceState serviceState) {
- synchronized(mLock) {
- mOnServiceStateChangedCalled = true;
- mLock.notify();
- }
- }
- };
- mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_SERVICE_STATE);
-
- Looper.loop();
- }
- });
-
- assertFalse(mOnServiceStateChangedCalled);
- t.start();
-
- synchronized (mLock) {
- if (!mOnServiceStateChangedCalled){
- mLock.wait(WAIT_TIME);
- }
- }
- t.checkException();
- assertTrue(mOnServiceStateChangedCalled);
-
- // reset and un-register
- mOnServiceStateChangedCalled = false;
- if (mListener != null) {
- // un-register the listener
- mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_NONE);
- }
- synchronized (mLock) {
- if (!mOnServiceStateChangedCalled){
- mLock.wait(WAIT_TIME);
- }
- }
- assertFalse(mOnServiceStateChangedCalled);
-
- // re-register the listener
- mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_SERVICE_STATE);
- synchronized (mLock) {
- if (!mOnServiceStateChangedCalled){
- mLock.wait(WAIT_TIME);
- }
- }
- t.checkException();
- assertTrue(mOnServiceStateChangedCalled);
- }
-}
diff --git a/tests/tests/telephony2/README.md b/tests/tests/telephony2/README.md
new file mode 100644
index 0000000..38deeca
--- /dev/null
+++ b/tests/tests/telephony2/README.md
@@ -0,0 +1,5 @@
+# Telephony2 CTS tests
+
+This directory contains the set of Telephony CTS tests used to exercise permissions.
+For instance, we may want to verify that an API call throws an exception without
+READ_PHONE_STATE.
diff --git a/tests/tests/telephony2/src/android/telephony2/cts/CallStateListenerPermissionTest.java b/tests/tests/telephony2/src/android/telephony2/cts/CallStateListenerPermissionTest.java
index b580930..25cd10c 100644
--- a/tests/tests/telephony2/src/android/telephony2/cts/CallStateListenerPermissionTest.java
+++ b/tests/tests/telephony2/src/android/telephony2/cts/CallStateListenerPermissionTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.telephony.PhoneStateListener;
@@ -63,6 +64,7 @@
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY));
}
/**
@@ -71,10 +73,6 @@
*/
@Test
public void testRegisterWithNoCallLogPermission() {
- if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
- return;
- }
-
TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
assertNotNull(telephonyManager);
@@ -97,10 +95,6 @@
*/
@Test
public void testCallStatePermission() throws Exception {
- if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
- return;
- }
-
TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
assertNotNull(telephonyManager);
MyTelephonyCallback callback = new MyTelephonyCallback();
diff --git a/tests/tests/telephony2/src/android/telephony2/cts/TelephonyManagerReadPhoneStatePermissionTest.java b/tests/tests/telephony2/src/android/telephony2/cts/TelephonyManagerReadPhoneStatePermissionTest.java
new file mode 100644
index 0000000..214a3f7
--- /dev/null
+++ b/tests/tests/telephony2/src/android/telephony2/cts/TelephonyManagerReadPhoneStatePermissionTest.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony2.cts;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
+import android.telecom.PhoneAccount;
+import android.telecom.TelecomManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.cts.TelephonyUtils;
+import android.telephony.emergency.EmergencyNumber;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test TelephonyManager APIs with READ_PHONE_STATE Permission.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Cannot grant the runtime permission in instant app mode")
+public class TelephonyManagerReadPhoneStatePermissionTest {
+
+ private boolean mHasTelephony;
+ TelephonyManager mTelephonyManager = null;
+ TelecomManager mTelecomManager = null;
+
+ @Before
+ public void setUp() throws Exception {
+ mHasTelephony = getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY);
+ mTelephonyManager =
+ (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
+ assertNotNull(mTelephonyManager);
+ mTelecomManager =
+ (TelecomManager) getContext().getSystemService(Context.TELECOM_SERVICE);
+ assertNotNull(mTelecomManager);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ TelephonyUtils.resetCompatCommand(InstrumentationRegistry.getInstrumentation(),
+ TelephonyUtils.CTS_APP_PACKAGE,
+ TelephonyUtils.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION_STRING);
+ }
+
+ /**
+ * Verify that TelephonyManager APIs requiring READ_PHONE_STATE Permission must work.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE}.
+ *
+ * APIs list:
+ * getDeviceSoftwareVersion()
+ * getCarrierConfig()
+ * getNetworkType()
+ * getDataNetworkType()
+ * getVoiceNetworkType()
+ * getGroupIdLevel1()
+ * getLine1AlphaTag()
+ * getVoiceMailNumber()
+ * getVisualVoicemailPackageName()
+ * getVoiceMailAlphaTag()
+ * getForbiddenPlmns()
+ * isDataRoamingEnabled()
+ * getSubscriptionId(@NonNull PhoneAccountHandle phoneAccountHandle)
+ * getServiceState()
+ * getEmergencyNumberList()
+ * getEmergencyNumberList(@EmergencyServiceCategories int categories)
+ * getPreferredOpportunisticDataSubscription()
+ * isModemEnabledForSlot(int slotIndex)
+ * isMultiSimSupported()
+ * doesSwitchMultiSimConfigTriggerReboot()
+ * getCallState() (when compat fwk enables enforcement)
+ * getCallStateForSubscription() (when compat fwk enables enforcement)
+ */
+ @Test
+ public void testTelephonyManagersAPIsRequiringReadPhoneStatePermissions() throws Exception {
+ if (!mHasTelephony) {
+ return;
+ }
+
+ try {
+ // We must ensure that compat fwk enables READ_PHONE_STATE enforcement
+ TelephonyUtils.enableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+ TelephonyUtils.CTS_APP_PACKAGE,
+ TelephonyUtils.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION_STRING);
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getCallState());
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getCallStateForSubscription());
+ } catch (SecurityException e) {
+ fail("TelephonyManager#getCallState and TelephonyManager#getCallStateForSubscription "
+ + "must not throw a SecurityException because READ_PHONE_STATE permission is "
+ + "granted and TelecomManager#ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION is "
+ + "enabled.");
+ }
+
+ int subId = mTelephonyManager.getSubscriptionId();
+
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getNetworkType());
+ } catch (SecurityException e) {
+ fail("getNetworkType() must not throw a SecurityException with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getDeviceSoftwareVersion());
+ } catch (SecurityException e) {
+ fail("getDeviceSoftwareVersion() must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getCarrierConfig());
+ } catch (SecurityException e) {
+ fail("getCarrierConfig() must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getDataNetworkType());
+ } catch (SecurityException e) {
+ fail("getDataNetworkType() must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getVoiceNetworkType());
+ } catch (SecurityException e) {
+ fail("getVoiceNetworkType() must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getGroupIdLevel1());
+ } catch (SecurityException e) {
+ fail("getGroupIdLevel1() must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getLine1AlphaTag());
+ } catch (SecurityException e) {
+ fail("getLine1AlphaTag() must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getVoiceMailNumber());
+ } catch (SecurityException e) {
+ fail("getVoiceMailNumber() must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getVisualVoicemailPackageName());
+ } catch (SecurityException e) {
+ fail("getVisualVoicemailPackageName() must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getVoiceMailAlphaTag());
+ } catch (SecurityException e) {
+ fail("getVoiceMailAlphaTag() must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getForbiddenPlmns());
+ } catch (SecurityException e) {
+ fail("getForbiddenPlmns() must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.isDataRoamingEnabled());
+ } catch (SecurityException e) {
+ fail("isDataRoamingEnabled() must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getSubscriptionId(
+ mTelecomManager.getDefaultOutgoingPhoneAccount(
+ PhoneAccount.SCHEME_TEL)));
+ } catch (SecurityException e) {
+ fail("getSubscriptionId(phoneAccountHandle) must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getServiceState());
+ } catch (SecurityException e) {
+ fail("getServiceState() must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getEmergencyNumberList());
+ } catch (SecurityException e) {
+ fail("getEmergencyNumberList() must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getEmergencyNumberList(
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE));
+ } catch (SecurityException e) {
+ fail("getEmergencyNumberList(EMERGENCY_SERVICE_CATEGORY_POLICE) must"
+ + " not throw a SecurityException with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.getPreferredOpportunisticDataSubscription());
+ } catch (SecurityException e) {
+ fail("getPreferredOpportunisticDataSubscription() must not throw"
+ + " a SecurityException with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.isModemEnabledForSlot(
+ SubscriptionManager.getSlotIndex(subId)));
+ } catch (SecurityException e) {
+ fail("isModemEnabledForSlot(slotIndex) must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.isMultiSimSupported());
+ } catch (SecurityException e) {
+ fail("isMultiSimSupported() must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> tm.doesSwitchMultiSimConfigTriggerReboot());
+ } catch (SecurityException e) {
+ fail("doesSwitchMultiSimConfigTriggerReboot() must not throw a SecurityException"
+ + " with READ_PHONE_STATE" + e);
+ }
+ }
+
+ private static Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+}
diff --git a/tests/tests/telephony3/Android.bp b/tests/tests/telephony3/Android.bp
index 5284cda..e5697e5 100644
--- a/tests/tests/telephony3/Android.bp
+++ b/tests/tests/telephony3/Android.bp
@@ -19,7 +19,10 @@
android_test {
name: "CtsTelephony3TestCases",
defaults: ["cts_defaults"],
- static_libs: ["ctstestrunner-axt"],
+ static_libs: [
+ "ctstestrunner-axt",
+ "compatibility-device-util-axt",
+ ],
srcs: ["src/**/*.java"],
// The SDK version is set to 28 to test device identifier access for apps with
// the READ_PHONE_STATE permission targeting pre-Q.
diff --git a/tests/tests/telephony3/AndroidManifest.xml b/tests/tests/telephony3/AndroidManifest.xml
index 3a53a6c..22372b8 100644
--- a/tests/tests/telephony3/AndroidManifest.xml
+++ b/tests/tests/telephony3/AndroidManifest.xml
@@ -18,6 +18,7 @@
package="android.telephony3.cts">
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/tests/tests/telephony3/README.md b/tests/tests/telephony3/README.md
new file mode 100644
index 0000000..b99b747
--- /dev/null
+++ b/tests/tests/telephony3/README.md
@@ -0,0 +1,4 @@
+# Telephony3 CTS tests
+
+This directory contains the set of Telephony CTS tests which verify behavior on
+sdk28 (Android P, which was released in 2018).
diff --git a/tests/tests/telephony3/src/android/telephony3/cts/CellInfoTest.java b/tests/tests/telephony3/src/android/telephony3/cts/CellInfoTest.java
new file mode 100644
index 0000000..9eb6aa0
--- /dev/null
+++ b/tests/tests/telephony3/src/android/telephony3/cts/CellInfoTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony3.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.telephony.CellInfo;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+public class CellInfoTest {
+ private static final String TAG = "CellInfoTest";
+
+ private static final int MAX_WAIT_SECONDS = 15;
+ private static final int POLL_INTERVAL_MILLIS = 1000;
+
+ private static final String[] sPermissions = new String[] {
+ android.Manifest.permission.READ_PHONE_STATE,
+ android.Manifest.permission.ACCESS_COARSE_LOCATION};
+
+ private TelephonyManager mTm;
+ private PackageManager mPm;
+
+ private boolean isCamped() {
+ return (mTm.getVoiceNetworkType() != TelephonyManager.NETWORK_TYPE_UNKNOWN
+ || mTm.getDataNetworkType() != TelephonyManager.NETWORK_TYPE_UNKNOWN);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mTm = (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
+ mPm = getContext().getPackageManager();
+
+ for (String permission : sPermissions) {
+ assertTrue("Something (not this test) has denied needed permission=" + permission,
+ getContext().checkSelfPermission(permission)
+ == android.content.pm.PackageManager.PERMISSION_GRANTED);
+ }
+ }
+
+ @Test
+ public void testCellInfoSdk28() {
+ if (!mPm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
+ return;
+ }
+
+ if (!isCamped()) fail("Device is not camped to a cell");
+
+ List<CellInfo> cellInfo = ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTm, TelephonyManager::getAllCellInfo);
+
+ // getAllCellInfo should never return null, and there should be at least one entry.
+ assertNotNull("TelephonyManager.getAllCellInfo() returned NULL CellInfo", cellInfo);
+ assertFalse("TelephonyManager.getAllCellInfo() returned an empty list", cellInfo.isEmpty());
+
+ final long initialTime = cellInfo.get(0).getTimeStamp();
+
+ for (int i = 0; i < MAX_WAIT_SECONDS; i++) {
+ try {
+ Thread.sleep(POLL_INTERVAL_MILLIS); // 1 second
+ } catch (InterruptedException ie) {
+ fail("Thread was interrupted");
+ }
+ List<CellInfo> newCellInfo = ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTm, TelephonyManager::getAllCellInfo);
+ assertNotNull("TelephonyManager.getAllCellInfo() returned NULL CellInfo", newCellInfo);
+ assertFalse("TelephonyManager.getAllCellInfo() returned an empty list",
+ newCellInfo.isEmpty());
+ // Test that new CellInfo has been retrieved from the modem
+ if (newCellInfo.get(0).getTimeStamp() != initialTime) return;
+ }
+ fail("CellInfo failed to update after " + MAX_WAIT_SECONDS + " seconds.");
+ }
+}
diff --git a/tests/tests/telephony3/src/android/telephony3/cts/PhoneStateListenerTest.java b/tests/tests/telephony3/src/android/telephony3/cts/PhoneStateListenerTest.java
new file mode 100644
index 0000000..01b3a2a
--- /dev/null
+++ b/tests/tests/telephony3/src/android/telephony3/cts/PhoneStateListenerTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+package android.telephony3.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Looper;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.test.filters.FlakyTest;
+
+import com.android.compatibility.common.util.TestThread;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+@FlakyTest
+public class PhoneStateListenerTest {
+
+ public static final long WAIT_TIME = 1000;
+ private boolean mOnServiceStateChangedCalled;
+ private TelephonyManager mTelephonyManager;
+ private PhoneStateListener mListener;
+ private final Object mLock = new Object();
+ private boolean mHasTelephony = true;
+ private static final String TAG = "android.telephony.cts.PhoneStateListenerTest";
+
+ @Before
+ public void setUp() throws Exception {
+ mTelephonyManager =
+ (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
+
+ PackageManager packageManager = getContext().getPackageManager();
+ if (packageManager != null) {
+ if (!packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ Log.d(TAG, "Some tests requiring FEATURE_TELEPHONY will be skipped");
+ mHasTelephony = false;
+ }
+ }
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mListener != null) {
+ // unregister the listener
+ mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_NONE);
+ }
+ }
+
+ @Test
+ public void testPhoneStateListener() {
+ Looper.prepare();
+ new PhoneStateListener();
+ }
+
+ @Test
+ public void testOnUnRegisterFollowedByRegister() throws Throwable {
+ if (!mHasTelephony) {
+ return;
+ }
+
+ TestThread t = new TestThread(new Runnable() {
+ public void run() {
+ Looper.prepare();
+
+ mListener = new PhoneStateListener() {
+ @Override
+ public void onServiceStateChanged(ServiceState serviceState) {
+ synchronized (mLock) {
+ mOnServiceStateChangedCalled = true;
+ mLock.notify();
+ }
+ }
+ };
+ mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_SERVICE_STATE);
+
+ Looper.loop();
+ }
+ });
+
+ assertFalse(mOnServiceStateChangedCalled);
+ t.start();
+
+ synchronized (mLock) {
+ if (!mOnServiceStateChangedCalled) {
+ mLock.wait(WAIT_TIME);
+ }
+ }
+ t.checkException();
+ assertTrue(mOnServiceStateChangedCalled);
+
+ // reset and un-register
+ mOnServiceStateChangedCalled = false;
+ if (mListener != null) {
+ // un-register the listener
+ mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_NONE);
+ }
+ synchronized (mLock) {
+ if (!mOnServiceStateChangedCalled) {
+ mLock.wait(WAIT_TIME);
+ }
+ }
+ assertFalse(mOnServiceStateChangedCalled);
+
+ // re-register the listener
+ mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_SERVICE_STATE);
+ synchronized (mLock) {
+ if (!mOnServiceStateChangedCalled) {
+ mLock.wait(WAIT_TIME);
+ }
+ }
+ t.checkException();
+ assertTrue(mOnServiceStateChangedCalled);
+ }
+}
diff --git a/tests/tests/telephony4/README.md b/tests/tests/telephony4/README.md
new file mode 100644
index 0000000..2a41615
--- /dev/null
+++ b/tests/tests/telephony4/README.md
@@ -0,0 +1,4 @@
+# Telephony4 CTS tests
+
+This directory contains the set of Telephony CTS tests which involve carrier privileges.
+The test apk is signed with android_telephony_cts_testkey.
diff --git a/tests/tests/text/OWNERS b/tests/tests/text/OWNERS
index 73d670f6..b52adb9 100644
--- a/tests/tests/text/OWNERS
+++ b/tests/tests/text/OWNERS
@@ -4,6 +4,5 @@
siyamed@google.com
nona@google.com
clarabayarri@google.com
-nfuller@google.com
ngeoffray@google.com
vichang@google.com
diff --git a/tests/tests/text/assets/fonts/LowGlyphFont.ttf b/tests/tests/text/assets/fonts/LowGlyphFont.ttf
new file mode 100644
index 0000000..751926e
--- /dev/null
+++ b/tests/tests/text/assets/fonts/LowGlyphFont.ttf
Binary files differ
diff --git a/tests/tests/text/assets/fonts/LowGlyphFont.ttx b/tests/tests/text/assets/fonts/LowGlyphFont.ttx
new file mode 100644
index 0000000..f613ee8
--- /dev/null
+++ b/tests/tests/text/assets/fonts/LowGlyphFont.ttx
@@ -0,0 +1,188 @@
+<?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"/>
+ </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="-1000"/>
+ <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="a" width="1000" lsb="93"/> <!-- 3em -->
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="3" platEncID="10" language="0">
+ <map code="0x0061" name="a" />
+ </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="-1000" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="-1000" on="1" />
+ <pt x="0" y="1000" on="1" />
+ <pt x="1000" y="1000" on="1" />
+ <pt x="1000" y="-1000" on="1" />
+ </contour>
+ <instructions />
+ </TTGlyph>
+ </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">
+ LowGlyph Test Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ LowGlyph Test 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/text/assets/fonts/StaticLayoutTouchLocation.ttf b/tests/tests/text/assets/fonts/StaticLayoutTouchLocation.ttf
new file mode 100644
index 0000000..42a7362b
--- /dev/null
+++ b/tests/tests/text/assets/fonts/StaticLayoutTouchLocation.ttf
Binary files differ
diff --git a/tests/tests/text/assets/fonts/StaticLayoutTouchLocation.ttx b/tests/tests/text/assets/fonts/StaticLayoutTouchLocation.ttx
new file mode 100644
index 0000000..c7ad6e6
--- /dev/null
+++ b/tests/tests/text/assets/fonts/StaticLayoutTouchLocation.ttx
@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS
+ WITHOUT 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"/>
+ </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="Thu Feb 15 18:29:10 2018"/>
+ <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="100"/>
+ <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="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="3" platEncID="10" language="0">
+ <map code="0x0020" name="1em" /> <!-- SPACE -->
+ <map code="0x0061" name="1em" /> <!-- SPACE -->
+ <map code="0x0627" name="1em" /> <!-- SPACE -->
+ </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="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="1000" y="500" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ </contour>
+ <instructions />
+ </TTGlyph>
+ </glyf>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright (C) 2021 The Android Open Source Project
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font For Touch Location Test
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font For Touch Location Test
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SampleFontForTouchLocationTest-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/text/assets/fonts/TallGlyphFont.ttf b/tests/tests/text/assets/fonts/TallGlyphFont.ttf
new file mode 100644
index 0000000..b6768e3
--- /dev/null
+++ b/tests/tests/text/assets/fonts/TallGlyphFont.ttf
Binary files differ
diff --git a/tests/tests/text/assets/fonts/TallGlyphFont.ttx b/tests/tests/text/assets/fonts/TallGlyphFont.ttx
new file mode 100644
index 0000000..5855933
--- /dev/null
+++ b/tests/tests/text/assets/fonts/TallGlyphFont.ttx
@@ -0,0 +1,188 @@
+<?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="U+1000"/>
+ </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="3000"/>
+ <descent value="-3000"/>
+ <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="U+1000" width="3000" lsb="93"/> <!-- 3em -->
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="3" platEncID="10" language="0">
+ <map code="0x1000" name="U+1000" />
+ </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="U+1000" xMin="0" yMin="-3000" xMax="3000" yMax="3000">
+ <contour>
+ <pt x="0" y="-3000" on="1" />
+ <pt x="0" y="3000" on="1" />
+ <pt x="3000" y="3000" on="1" />
+ <pt x="3000" y="-3000" on="1" />
+ </contour>
+ <instructions />
+ </TTGlyph>
+ </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">
+ TallGlyph Test Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ TallGlyph Test 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/text/src/android/text/cts/BoringLayoutFallbackLineSpacingTest.java b/tests/tests/text/src/android/text/cts/BoringLayoutFallbackLineSpacingTest.java
new file mode 100644
index 0000000..9c59b62
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/BoringLayoutFallbackLineSpacingTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 com.google.common.truth.Truth.assertThat;
+
+import android.content.res.AssetManager;
+import android.graphics.Typeface;
+import android.graphics.fonts.Font;
+import android.graphics.fonts.FontFamily;
+import android.platform.test.annotations.Presubmit;
+import android.text.BoringLayout;
+import android.text.Layout;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextDirectionHeuristics;
+import android.text.TextPaint;
+import android.text.style.TypefaceSpan;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+/**
+ * Tests StaticLayout vertical metrics behavior.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BoringLayoutFallbackLineSpacingTest {
+
+ @Test
+ public void testFallbackSpacing_Metrics() throws IOException {
+ AssetManager am =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getAssets();
+ Typeface typeface = new Typeface.CustomFallbackBuilder(
+ new FontFamily.Builder(
+ new Font.Builder(am, "fonts/LowGlyphFont.ttf").build()
+ ).build()
+ ).addCustomFallback(
+ new FontFamily.Builder(
+ new Font.Builder(am, "fonts/TallGlyphFont.ttf").build()
+ ).build()
+ ).build();
+
+ TextPaint p = new TextPaint();
+ p.setTextSize(100); // make 1 em = 100 pixels
+ p.setTypeface(typeface);
+
+ String text = "a\u1000";
+ BoringLayout.Metrics boring = BoringLayout.isBoring(
+ text, p, TextDirectionHeuristics.LTR, false /* useFallbackLineSpacing */, null);
+
+ BoringLayout.Metrics fallbackLineSpacingBoring = BoringLayout.isBoring(
+ text, p, TextDirectionHeuristics.LTR, true /* useFallbackLineSpacing */, null);
+
+
+ // LowGlyphFont has -1 em ascent and 1 em descent. TallGlyphFont has -3em ascent and 3em
+ // descent.
+ assertThat(boring.ascent).isEqualTo(-100);
+ assertThat(boring.descent).isEqualTo(100);
+ assertThat(fallbackLineSpacingBoring.ascent).isEqualTo(-300);
+ assertThat(fallbackLineSpacingBoring.descent).isEqualTo(300);
+ }
+
+ @Test
+ public void testFallbackSpacing_Layout() throws IOException {
+ AssetManager am =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getAssets();
+ Typeface typeface = new Typeface.CustomFallbackBuilder(
+ new FontFamily.Builder(
+ new Font.Builder(am, "fonts/LowGlyphFont.ttf").build()
+ ).build()
+ ).addCustomFallback(
+ new FontFamily.Builder(
+ new Font.Builder(am, "fonts/TallGlyphFont.ttf").build()
+ ).build()
+ ).build();
+
+ TextPaint p = new TextPaint();
+ p.setTextSize(100); // make 1 em = 100 pixels
+ p.setTypeface(typeface);
+
+ String text = "a\u1000";
+ BoringLayout.Metrics metrics = BoringLayout.isBoring(text, p, TextDirectionHeuristics.LTR,
+ false /* useFallbackLineSpacing */, null);
+ BoringLayout layout = BoringLayout.make(
+ text, p,
+ Integer.MAX_VALUE /* outer width */,
+ Layout.Alignment.ALIGN_NORMAL,
+ 1.0f /* spacing mult */,
+ 0.0f /* spacing add */,
+ metrics,
+ false /* includePad */);
+ BoringLayout includePadLayout = BoringLayout.make(
+ text, p,
+ Integer.MAX_VALUE /* outer width */,
+ Layout.Alignment.ALIGN_NORMAL,
+ 1.0f /* spacing mult */,
+ 0.0f /* spacing add */,
+ metrics,
+ true /* includePad */);
+
+ BoringLayout.Metrics fallbackMetrics = BoringLayout.isBoring(text, p,
+ TextDirectionHeuristics.LTR, true /* useFallbackLineSpacing */, null);
+ BoringLayout fallbackLayout = BoringLayout.make(
+ text, p,
+ Integer.MAX_VALUE /* outer width */,
+ Layout.Alignment.ALIGN_NORMAL,
+ 1.0f /* spacing mult */,
+ 0.0f /* spacing add */,
+ fallbackMetrics,
+ false /* includePad */);
+ BoringLayout includePadFallbackLayout = BoringLayout.make(
+ text, p,
+ Integer.MAX_VALUE /* outer width */,
+ Layout.Alignment.ALIGN_NORMAL,
+ 1.0f /* spacing mult */,
+ 0.0f /* spacing add */,
+ fallbackMetrics,
+ true /* includePad */);
+
+
+ // LowGlyphFont has -1 em ascent and 1 em descent. TallGlyphFont has -3em ascent and 3em
+ // descent.
+ assertThat(layout.getLineBottom(0)).isEqualTo(200);
+ assertThat(includePadLayout.getLineBottom(0)).isEqualTo(200);
+ assertThat(fallbackLayout.getLineBottom(0)).isEqualTo(600);
+ assertThat(includePadFallbackLayout.getLineBottom(0)).isEqualTo(600);
+ }
+
+ @Test
+ public void testReplacementSpans() {
+ SpannableString ss = new SpannableString("Hello, World.");
+ ss.setSpan(new TypefaceSpan(Typeface.SERIF), 5, 10, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ TextPaint paint = new TextPaint();
+
+ BoringLayout.isBoring(ss, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, true, null);
+ }
+
+}
diff --git a/tests/tests/text/src/android/text/cts/FallbackLineSpacingTest.java b/tests/tests/text/src/android/text/cts/FallbackLineSpacingTest.java
new file mode 100644
index 0000000..2717cc4
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/FallbackLineSpacingTest.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 com.google.common.truth.Truth.assertThat;
+
+import android.content.res.AssetManager;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.fonts.Font;
+import android.graphics.fonts.FontFamily;
+import android.platform.test.annotations.Presubmit;
+import android.text.TextPaint;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+/**
+ * Tests StaticLayout vertical metrics behavior.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FallbackLineSpacingTest {
+
+ @Test
+ public void testFallbackSpacing() throws IOException {
+ AssetManager am =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getAssets();
+ Typeface typeface = new Typeface.CustomFallbackBuilder(
+ new FontFamily.Builder(
+ new Font.Builder(am, "fonts/LowGlyphFont.ttf").build()
+ ).build()
+ ).addCustomFallback(
+ new FontFamily.Builder(
+ new Font.Builder(am, "fonts/TallGlyphFont.ttf").build()
+ ).build()
+ ).build();
+
+ TextPaint p = new TextPaint();
+ p.setTextSize(100); // make 1 em = 100 pixels
+ p.setTypeface(typeface);
+
+ String text = "a\u1000";
+
+ Paint.FontMetricsInt fmi = new Paint.FontMetricsInt();
+ p.getFontMetricsInt(text, 0, text.length(), 0, text.length(), false, fmi);
+ assertThat(fmi.top).isEqualTo(-300);
+ assertThat(fmi.ascent).isEqualTo(-300);
+ assertThat(fmi.descent).isEqualTo(300);
+ assertThat(fmi.bottom).isEqualTo(300);
+
+ p.getFontMetricsInt(text, 0, 1, 0, text.length(), false, fmi);
+ assertThat(fmi.top).isEqualTo(-100);
+ assertThat(fmi.ascent).isEqualTo(-100);
+ assertThat(fmi.descent).isEqualTo(100);
+ assertThat(fmi.bottom).isEqualTo(100);
+
+ p.getFontMetricsInt(text, 1, 1, 0, text.length(), false, fmi);
+ assertThat(fmi.top).isEqualTo(-300);
+ assertThat(fmi.ascent).isEqualTo(-300);
+ assertThat(fmi.descent).isEqualTo(300);
+ assertThat(fmi.bottom).isEqualTo(300);
+ }
+
+ @Test
+ public void testEmptyString() {
+ Paint.FontMetricsInt fmi = new Paint.FontMetricsInt();
+ Paint paint = new Paint();
+ paint.setTextSize(100); // make 1em = 100 pixels
+ paint.getFontMetricsInt("a", 0, 0, 0, 0, false, fmi);
+
+ Paint.FontMetricsInt noTextFmi = paint.getFontMetricsInt();
+
+ assertThat(fmi).isEqualTo(noTextFmi);
+ }
+
+ @Test
+ public void testEmptyCharArray() {
+ Paint.FontMetricsInt fmi = new Paint.FontMetricsInt();
+ Paint paint = new Paint();
+ paint.setTextSize(100); // make 1em = 100 pixels
+ paint.getFontMetricsInt("a".toCharArray(), 0, 0, 0, 0, false, fmi);
+
+ Paint.FontMetricsInt noTextFmi = paint.getFontMetricsInt();
+
+ assertThat(fmi).isEqualTo(noTextFmi);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNullTextArgument_String() {
+ new Paint().getFontMetricsInt((String) null, 0, 0, 0, 0, false,
+ new Paint.FontMetricsInt());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNullTextArgument_CharArgument() {
+ new Paint().getFontMetricsInt((char[]) null, 0, 0, 0, 0, false,
+ new Paint.FontMetricsInt());
+ }
+
+ // start argument test cases
+ @Test(expected = IllegalArgumentException.class)
+ public void testSmallStartArgument_String() {
+ new Paint().getFontMetricsInt("a", -1, 0, 0, 0, false,
+ new Paint.FontMetricsInt());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSmallStartArgument_CharArray() {
+ new Paint().getFontMetricsInt("a".toCharArray(), -1, 0, 0, 0, false,
+ new Paint.FontMetricsInt());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testLargeStartArgument_String() {
+ new Paint().getFontMetricsInt("a", 2, 0, 0, 0, false,
+ new Paint.FontMetricsInt());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testLargeStartArgument_CharArray() {
+ new Paint().getFontMetricsInt("a".toCharArray(), 2, 0, 0, 0, false,
+ new Paint.FontMetricsInt());
+ }
+
+ // count argument test cases
+ @Test(expected = IllegalArgumentException.class)
+ public void testSmallCountArgument_String() {
+ new Paint().getFontMetricsInt("a", 0, -1, 0, 0, false,
+ new Paint.FontMetricsInt());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSmalCountArgument_CharArray() {
+ new Paint().getFontMetricsInt("a".toCharArray(), 0, -1, 0, 0, false,
+ new Paint.FontMetricsInt());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testLargeCountArgument_String() {
+ new Paint().getFontMetricsInt("a", 0, 2, 0, 0, false,
+ new Paint.FontMetricsInt());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testLargeCountArgument_CharArray() {
+ new Paint().getFontMetricsInt("a".toCharArray(), 0, 2, 0, 0, false,
+ new Paint.FontMetricsInt());
+ }
+
+ // ctxStart argument test cases
+ @Test(expected = IllegalArgumentException.class)
+ public void testSmallCtxStartArgument_String() {
+ new Paint().getFontMetricsInt("a", 0, 1, -1, 0, false,
+ new Paint.FontMetricsInt());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSmallCtxStartArgument_CharArray() {
+ new Paint().getFontMetricsInt("a".toCharArray(), 0, 1, -1, 0, false,
+ new Paint.FontMetricsInt());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testLargeCtxStartArgument_String() {
+ new Paint().getFontMetricsInt("a", 0, 1, 2, 0, false,
+ new Paint.FontMetricsInt());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testLargeCtxStartArgument_CharArray() {
+ new Paint().getFontMetricsInt("a".toCharArray(), 0, 1, 2, 0, false,
+ new Paint.FontMetricsInt());
+ }
+
+ // count argument test cases
+ @Test(expected = IllegalArgumentException.class)
+ public void testSmallCtxCountArgument_String() {
+ new Paint().getFontMetricsInt("a", 0, 1, 0, -1, false,
+ new Paint.FontMetricsInt());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSmalCtxCountArgument_CharArray() {
+ new Paint().getFontMetricsInt("a".toCharArray(), 0, 1, 0, -1, false,
+ new Paint.FontMetricsInt());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testLargeCtxCountArgument_String() {
+ new Paint().getFontMetricsInt("a", 0, 1, 0, 2, false,
+ new Paint.FontMetricsInt());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testLargeCtxCountArgument_CharArray() {
+ new Paint().getFontMetricsInt("a".toCharArray(), 0, 1, 0, 2, false,
+ new Paint.FontMetricsInt());
+ }
+
+ // count argument test cases
+ @Test(expected = IllegalArgumentException.class)
+ public void testSmallNullOutArgument_String() {
+ new Paint().getFontMetricsInt("a", 0, 1, 0, 1, false, null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSmalNullOutArgument_CharArray() {
+ new Paint().getFontMetricsInt("a".toCharArray(), 0, 1, 0, 1, false, null);
+ }
+
+}
diff --git a/tests/tests/text/src/android/text/cts/FontFileTestUtil.java b/tests/tests/text/src/android/text/cts/FontFileTestUtil.java
new file mode 100644
index 0000000..6fbab1f
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/FontFileTestUtil.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
+
+public class FontFileTestUtil {
+ private static final int SFNT_VERSION_1 = 0x00010000;
+ private static final int SFNT_VERSION_OTTO = 0x4F54544F;
+ private static final int TTC_TAG = 0x74746366;
+ private static final int NAME_TAG = 0x6E616D65;
+ private static final int GPOS_TAG = 0x47504F53;
+ private static final int CHWS_TAG = 0x63687773;
+
+ public static String getPostScriptName(File file, int index) {
+ try (FileInputStream fis = new FileInputStream(file)) {
+ final FileChannel fc = fis.getChannel();
+ long size = fc.size();
+ ByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, size)
+ .order(ByteOrder.BIG_ENDIAN);
+
+ int magicNumber = buffer.getInt(0);
+
+ int fontOffset = 0;
+ int numFonts = buffer.getInt(8);
+ if (index >= numFonts) {
+ return null;
+ }
+
+ if (magicNumber == TTC_TAG) {
+ fontOffset = buffer.getInt(12 + 4 * index);
+ magicNumber = buffer.getInt(fontOffset);
+ if (magicNumber != SFNT_VERSION_1 && magicNumber != SFNT_VERSION_OTTO) {
+ throw new IOException("Unknown magic number at 0th font: #" + magicNumber);
+ }
+ } else if (magicNumber != SFNT_VERSION_1 && magicNumber != SFNT_VERSION_OTTO) {
+ throw new IOException("Unknown magic number: #" + magicNumber);
+ }
+
+ int numTables = buffer.getShort(fontOffset + 4); // offset to number of table
+ int nameTableOffset = 0;
+ for (int i = 0; i < numTables; ++i) {
+ int tableEntryOffset = fontOffset + 12 + i * 16;
+ int tableTag = buffer.getInt(tableEntryOffset);
+ if (tableTag == NAME_TAG) {
+ nameTableOffset = buffer.getInt(tableEntryOffset + 8);
+ break;
+ }
+ }
+
+ if (nameTableOffset == 0) {
+ throw new IOException("name table not found.");
+ }
+
+ int nameTableCount = buffer.getShort(nameTableOffset + 2);
+ int storageOffset = buffer.getShort(nameTableOffset + 4);
+
+ for (int i = 0; i < nameTableCount; ++i) {
+ int platformID = buffer.getShort(nameTableOffset + 6 + i * 12);
+ int encodingID = buffer.getShort(nameTableOffset + 6 + i * 12 + 2);
+ int languageID = buffer.getShort(nameTableOffset + 6 + i * 12 + 4);
+ int nameID = buffer.getShort(nameTableOffset + 6 + i * 12 + 6);
+ int length = buffer.getShort(nameTableOffset + 6 + i * 12 + 8);
+ int stringOffset = buffer.getShort(nameTableOffset + 6 + i * 12 + 10);
+
+ if (nameID == 6 && platformID == 3 && encodingID == 1 && languageID == 1033) {
+ byte[] name = new byte[length];
+ ByteBuffer slice = buffer.slice();
+ slice.position(nameTableOffset + storageOffset + stringOffset);
+ slice.get(name);
+ // encoded in UTF-16BE for platform ID = 3
+ return new String(name, StandardCharsets.UTF_16BE);
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return null;
+ }
+}
diff --git a/tests/tests/text/src/android/text/cts/NotoCJKFontRequirement.java b/tests/tests/text/src/android/text/cts/NotoCJKFontRequirement.java
new file mode 100644
index 0000000..ef751ce
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/NotoCJKFontRequirement.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 com.google.common.truth.Truth.assertThat;
+
+import android.content.res.Resources;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.fonts.Font;
+import android.graphics.fonts.FontFamily;
+import android.graphics.fonts.SystemFonts;
+import android.graphics.text.PositionedGlyphs;
+import android.graphics.text.TextRunShaper;
+import android.icu.util.ULocale;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NotoCJKFontRequirement {
+
+ private static final String TEST_OPEN_DOUBLE_KEY_BRACKET = "\u300C\u300E";
+
+ private static final Set<String> CJK_SCRIPT = new HashSet<>(Arrays.asList(
+ "Hani", // General Han character
+ "Hans", // Simplified Chinese
+ "Hant", // Tranditional Chinese
+ "Hira", "Hrkt", "Jpan", "Kana", // Japanese
+ "Hang", "Kore" // Hangul
+ ));
+
+ private boolean isCJKSupported() {
+ final String[] localeNames = Resources.getSystem().getStringArray(
+ Resources.getSystem().getIdentifier("supported_locales", "array", "android"));
+ for (String locale : localeNames) {
+ final ULocale uLocale = ULocale.addLikelySubtags(ULocale.forLanguageTag(locale));
+ final String script = uLocale.getScript();
+
+ if (CJK_SCRIPT.contains(script)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // List of CJK font configuration.
+ private static final Set<String> CJK_FONT_PSNAMES = new HashSet<>();
+
+ static {
+ CJK_FONT_PSNAMES.add("NotoSansCJKsc-Regular");
+ CJK_FONT_PSNAMES.add("NotoSansCJKtc-Regular");
+ CJK_FONT_PSNAMES.add("NotoSansCJKjp-Regular");
+ CJK_FONT_PSNAMES.add("NotoSansCJKkr-Regular");
+ CJK_FONT_PSNAMES.add("NotoSerifCJKsc-Regular");
+ CJK_FONT_PSNAMES.add("NotoSerifCJKtc-Regular");
+ CJK_FONT_PSNAMES.add("NotoSerifCJKjp-Regular");
+ CJK_FONT_PSNAMES.add("NotoSerifCJKkr-Regular");
+ };
+
+ // Returns list of Noto CJK fonts.
+ private static List<Font> getNotoCJKFonts() {
+ final ArrayList<Font> cjkFonts = new ArrayList<>();
+
+ for (Font font : SystemFonts.getAvailableFonts()) {
+ String psName = FontFileTestUtil.getPostScriptName(font.getFile(), font.getTtcIndex());
+ if (CJK_FONT_PSNAMES.contains(psName)) {
+ cjkFonts.add(font);
+ }
+ }
+
+ return cjkFonts;
+ }
+
+ @Test
+ public void testContextualSpacing() {
+ if (!isCJKSupported()) {
+ return; // If the device doesn't support CJK language, don't require chws font.
+ }
+
+ Paint paint = new Paint();
+
+ // Only expect chws feature on NotoCJK fonts.
+ getNotoCJKFonts().forEach(font -> {
+ Typeface tf = new Typeface.CustomFallbackBuilder(
+ new FontFamily.Builder(font).build()
+ ).build();
+
+ paint.setTextSize(100f); // Make 1em = 100px
+ paint.setTypeface(tf);
+ paint.setFontFeatureSettings("\"chws\" 0");
+
+ PositionedGlyphs offGlyphs = TextRunShaper.shapeTextRun(
+ TEST_OPEN_DOUBLE_KEY_BRACKET,
+ 0, TEST_OPEN_DOUBLE_KEY_BRACKET.length(),
+ 0, TEST_OPEN_DOUBLE_KEY_BRACKET.length(),
+ 0f, 0f, false /* LTR */, paint);
+
+ paint.setFontFeatureSettings("\"chws\" 1");
+
+ PositionedGlyphs onGlyphs = TextRunShaper.shapeTextRun(
+ TEST_OPEN_DOUBLE_KEY_BRACKET,
+ 0, TEST_OPEN_DOUBLE_KEY_BRACKET.length(),
+ 0, TEST_OPEN_DOUBLE_KEY_BRACKET.length(),
+ 0f, 0f, false /* LTR */, paint);
+
+ assertThat(onGlyphs.getGlyphX(1)).isLessThan(offGlyphs.getGlyphX(1));
+ });
+ }
+}
diff --git a/tests/tests/text/src/android/text/cts/PrecomputedTextTest.java b/tests/tests/text/src/android/text/cts/PrecomputedTextTest.java
index a0ecfc6..6bf0670 100644
--- a/tests/tests/text/src/android/text/cts/PrecomputedTextTest.java
+++ b/tests/tests/text/src/android/text/cts/PrecomputedTextTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -32,6 +33,7 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.Typeface;
+import android.graphics.text.LineBreakConfig;
import android.text.Layout;
import android.text.PrecomputedText;
import android.text.PrecomputedText.Params;
@@ -88,6 +90,14 @@
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
.setTextDirection(LTR).build());
+
+ LineBreakConfig lineBreakConfig = new LineBreakConfig();
+ lineBreakConfig.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT);
+ assertNotNull(new Params.Builder(PAINT)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+ .setLineBreakConfig(lineBreakConfig)
+ .setTextDirection(LTR).build());
}
@Test
@@ -99,6 +109,11 @@
.getHyphenationFrequency());
assertEquals(RTL, new Params.Builder(PAINT).setTextDirection(RTL).build()
.getTextDirection());
+
+ LineBreakConfig lineBreakConfig = new LineBreakConfig();
+ lineBreakConfig.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT);
+ assertTrue(lineBreakConfig.equals(new Params.Builder(PAINT)
+ .setLineBreakConfig(lineBreakConfig).build().getLineBreakConfig()));
}
@Test
@@ -109,13 +124,20 @@
new Params.Builder(PAINT).build().getHyphenationFrequency());
assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR,
new Params.Builder(PAINT).build().getTextDirection());
+
+ // Verify that there is no LineBreakConfig instance by default.
+ assertNull(new Params.Builder(PAINT).build().getLineBreakConfig());
}
@Test
public void testParams_equals() {
+ LineBreakConfig config = new LineBreakConfig();
+ config.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT);
+
final Params base = new Params.Builder(PAINT)
.setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+ .setLineBreakConfig(config)
.setTextDirection(LTR).build();
assertFalse(base.equals(null));
@@ -125,6 +147,7 @@
Params other = new Params.Builder(PAINT)
.setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+ .setLineBreakConfig(config)
.setTextDirection(LTR).build();
assertTrue(base.equals(other));
assertTrue(other.equals(base));
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutBidiTouchTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutBidiTouchTest.java
new file mode 100644
index 0000000..641b278
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutBidiTouchTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.assertNotNull;
+
+import android.content.res.AssetManager;
+import android.graphics.Typeface;
+import android.platform.test.annotations.AsbSecurityTest;
+import android.text.Layout;
+import android.text.SpannableString;
+import android.text.StaticLayout;
+import android.text.TextDirectionHeuristics;
+import android.text.TextPaint;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StaticLayoutBidiTouchTest {
+
+ @Test
+ @AsbSecurityTest(cveBugId = 193849901)
+ public void touchOffsetTest() {
+ AssetManager am = InstrumentationRegistry.getInstrumentation().getContext().getAssets();
+ TextPaint p = new TextPaint();
+ Typeface tf = new Typeface.Builder(am, "fonts/StaticLayoutTouchLocation.ttf").build();
+ assertNotNull(tf);
+ p.setTypeface(tf);
+ p.setTextSize(10f);
+
+ CharSequence text = new SpannableString("\u0627\u0020\u2066\u0020\u0061");
+ int width = 10;
+ Layout layout = StaticLayout.Builder.obtain(text, 0, text.length(), p, width)
+ .setTextDirection(TextDirectionHeuristics.RTL)
+ .build();
+
+ // Accessing from left with 20 dividing points.
+ for (int i = 0; i < layout.getLineCount(); ++i) {
+ for (float w = 0; w < layout.getLineWidth(i); w += layout.getLineWidth(i) / 20f) {
+ layout.getOffsetForHorizontal(i, w);
+ }
+ }
+ }
+}
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutFallbackLineSpacingTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutFallbackLineSpacingTest.java
new file mode 100644
index 0000000..8c6d761
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutFallbackLineSpacingTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 com.google.common.truth.Truth.assertThat;
+
+import android.content.res.AssetManager;
+import android.graphics.Typeface;
+import android.graphics.fonts.Font;
+import android.graphics.fonts.FontFamily;
+import android.graphics.text.PositionedGlyphs;
+import android.graphics.text.TextRunShaper;
+import android.platform.test.annotations.Presubmit;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+/**
+ * Tests StaticLayout vertical metrics behavior.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StaticLayoutFallbackLineSpacingTest {
+
+ @Test
+ public void testFallbackSpacing() throws IOException {
+ AssetManager am =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getAssets();
+ Typeface typeface = new Typeface.CustomFallbackBuilder(
+ new FontFamily.Builder(
+ new Font.Builder(am, "fonts/LowGlyphFont.ttf").build()
+ ).build()
+ ).addCustomFallback(
+ new FontFamily.Builder(
+ new Font.Builder(am, "fonts/TallGlyphFont.ttf").build()
+ ).build()
+ ).build();
+
+ TextPaint p = new TextPaint();
+ p.setTextSize(100); // make 1 em = 100 pixels
+ p.setTypeface(typeface);
+
+ PositionedGlyphs glyphs = TextRunShaper.shapeTextRun("a", 0, 1, 0, 1, 0, 0, false, p);
+ // LowGlyphFont has -1 em ascent and 1 em descent.
+ assertThat(glyphs.getAscent()).isEqualTo(-100);
+ assertThat(glyphs.getDescent()).isEqualTo(100);
+
+ glyphs = TextRunShaper.shapeTextRun("\u1000", 0, 1, 0, 1, 0, 0, false, p);
+ // TallGlyphFont has -3 em ascent and 3 em descent.
+ assertThat(glyphs.getAscent()).isEqualTo(-300);
+ assertThat(glyphs.getDescent()).isEqualTo(300);
+
+ String text = "a\u1000";
+ glyphs = TextRunShaper.shapeTextRun(
+ text, 0, text.length(), 0, text.length(), 0, 0, false, p);
+
+ // TallGlyphFont has -3em ascent and 3em descent.
+ assertThat(glyphs.getAscent()).isEqualTo(-300);
+ assertThat(glyphs.getDescent()).isEqualTo(300);
+
+ StaticLayout layout = StaticLayout.Builder.obtain(
+ text, 0, text.length(), p, Integer.MAX_VALUE)
+ .setUseLineSpacingFromFallbacks(true)
+ .setIncludePad(true)
+ .build();
+ assertThat(layout.getLineBottom(0)).isEqualTo(600);
+ }
+
+}
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingVariantsTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingVariantsTest.java
index f3781b6..85832fd 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingVariantsTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingVariantsTest.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.graphics.Typeface;
+import android.graphics.text.LineBreakConfig;
import android.os.LocaleList;
import android.text.StaticLayout;
import android.text.TextPaint;
@@ -47,13 +48,18 @@
return paint;
}
- private static StaticLayout buildLayout(String text, LocaleList locales, int width) {
- return StaticLayout.Builder.obtain(
- text, 0, text.length(), setupPaint(locales), width).build();
+ private static StaticLayout buildLayout(String text, LocaleList locales,
+ LineBreakConfig lineBreakConfig, int width) {
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(),
+ setupPaint(locales), width);
+ builder.setLineBreakConfig(lineBreakConfig);
+ return builder.build();
}
- private static void assertLineBreak(String text, String locale, int width, String... expected) {
- final StaticLayout layout = buildLayout(text, LocaleList.forLanguageTags(locale), width);
+ private static void assertLineBreak(String text, String locale,
+ LineBreakConfig lineBreakConfig, int width, String... expected) {
+ final StaticLayout layout = buildLayout(text, LocaleList.forLanguageTags(locale),
+ lineBreakConfig, width);
assertEquals(expected.length, layout.getLineCount());
int currentExpectedOffset = 0;
@@ -78,34 +84,36 @@
@Test
public void testBreakVariant_loose() {
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 90, SAMPLE_TEXT);
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 80,
+ LineBreakConfig config = new LineBreakConfig();
+ config.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_LOOSE);
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 90, SAMPLE_TEXT);
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 80,
"\u30D0\u30C3\u30C6\u30EA\u30FC\u30BB\u30FC\u30D0",
"\u30FC");
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 70,
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 70,
"\u30D0\u30C3\u30C6\u30EA\u30FC\u30BB\u30FC",
"\u30D0\u30FC");
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 60,
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 60,
"\u30D0\u30C3\u30C6\u30EA\u30FC\u30BB",
"\u30FC\u30D0\u30FC");
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 50,
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 50,
"\u30D0\u30C3\u30C6\u30EA\u30FC",
"\u30BB\u30FC\u30D0\u30FC");
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 40,
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 40,
"\u30D0\u30C3\u30C6\u30EA",
"\u30FC\u30BB\u30FC\u30D0",
"\u30FC");
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 30,
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 30,
"\u30D0\u30C3\u30C6",
"\u30EA\u30FC\u30BB",
"\u30FC\u30D0\u30FC");
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 20,
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 20,
"\u30D0\u30C3",
"\u30C6\u30EA",
"\u30FC\u30BB",
"\u30FC\u30D0",
"\u30FC");
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 10,
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 10,
"\u30D0",
"\u30C3",
"\u30C6",
@@ -119,35 +127,37 @@
@Test
public void testBreakVariant_strict() {
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 90, SAMPLE_TEXT);
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 80,
+ LineBreakConfig config = new LineBreakConfig();
+ config.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT);
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 90, SAMPLE_TEXT);
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 80,
"\u30D0\u30C3\u30C6\u30EA\u30FC\u30BB\u30FC",
"\u30D0\u30FC");
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 70,
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 70,
"\u30D0\u30C3\u30C6\u30EA\u30FC\u30BB\u30FC",
"\u30D0\u30FC");
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 60,
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 60,
"\u30D0\u30C3\u30C6\u30EA\u30FC",
"\u30BB\u30FC\u30D0\u30FC");
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 50,
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 50,
"\u30D0\u30C3\u30C6\u30EA\u30FC",
"\u30BB\u30FC\u30D0\u30FC");
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 40,
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 40,
"\u30D0\u30C3\u30C6",
"\u30EA\u30FC\u30BB\u30FC",
"\u30D0\u30FC");
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 30,
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 30,
"\u30D0\u30C3\u30C6",
"\u30EA\u30FC",
"\u30BB\u30FC",
"\u30D0\u30FC");
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 20,
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 20,
"\u30D0\u30C3",
"\u30C6",
"\u30EA\u30FC",
"\u30BB\u30FC",
"\u30D0\u30FC");
- assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 10,
+ assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 10,
"\u30D0",
"\u30C3",
"\u30C6",
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
index 456ca78..cb3e795 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
@@ -32,6 +32,7 @@
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.Typeface;
+import android.graphics.text.LineBreakConfig;
import android.os.LocaleList;
import android.platform.test.annotations.AsbSecurityTest;
import android.text.Editable;
@@ -238,6 +239,21 @@
StaticLayout layout = builder.build();
assertNotNull(layout);
}
+ {
+ // setLineBreakConfig
+ LineBreakConfig lineBreakConfig = new LineBreakConfig();
+ lineBreakConfig.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT);
+
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
+ LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ builder.setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY);
+ builder.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
+ builder.setIncludePad(true);
+ builder.setIndents(null, null);
+ builder.setLineBreakConfig(lineBreakConfig);
+ StaticLayout layout = builder.build();
+ assertNotNull(layout);
+ }
}
@Test
diff --git a/tests/tests/text/src/android/text/style/cts/StyleSpanTest.java b/tests/tests/text/src/android/text/style/cts/StyleSpanTest.java
index 723fa27..df4a6d9 100644
--- a/tests/tests/text/src/android/text/style/cts/StyleSpanTest.java
+++ b/tests/tests/text/src/android/text/style/cts/StyleSpanTest.java
@@ -19,7 +19,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import android.content.res.Configuration;
import android.graphics.Typeface;
+import android.graphics.fonts.FontStyle;
import android.os.Parcel;
import android.text.TextPaint;
import android.text.style.StyleSpan;
@@ -43,6 +45,8 @@
p.setDataPosition(0);
StyleSpan fromParcel = new StyleSpan(p);
assertEquals(2, fromParcel.getStyle());
+ assertEquals(Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED,
+ fromParcel.getFontWeightAdjustment());
new StyleSpan(-2);
} finally {
p.recycle();
@@ -59,7 +63,14 @@
}
@Test
- public void testUpdateMeasureState() {
+ public void testGetFontWeightAdjustment() {
+ StyleSpan styleSpan = new StyleSpan(2, 300);
+ assertEquals(300, styleSpan.getFontWeightAdjustment());
+ }
+
+
+ @Test
+ public void testUpdateMeasureState_withStyle() {
StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
TextPaint tp = new TextPaint();
@@ -75,6 +86,24 @@
assertEquals(Typeface.BOLD, tp.getTypeface().getStyle());
}
+ @Test
+ public void testUpdateMeasureState_withFontWeightAdjustment() {
+ StyleSpan styleSpan = new StyleSpan(Typeface.BOLD, 300);
+
+ TextPaint tp = new TextPaint();
+ Typeface tf = Typeface.defaultFromStyle(Typeface.NORMAL);
+ tp.setTypeface(tf);
+
+ assertNotNull(tp.getTypeface());
+ assertEquals(Typeface.NORMAL, tp.getTypeface().getStyle());
+
+ styleSpan.updateMeasureState(tp);
+
+ assertNotNull(tp.getTypeface());
+ assertEquals(Typeface.BOLD, tp.getTypeface().getStyle());
+ assertEquals(tp.getTypeface().getWeight(), FontStyle.FONT_WEIGHT_MAX);
+ }
+
@Test(expected=NullPointerException.class)
public void testUpdateMeasureStateNull() {
StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
@@ -83,7 +112,7 @@
}
@Test
- public void testUpdateDrawState() {
+ public void testUpdateDrawState_withStyle() {
StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
TextPaint tp = new TextPaint();
@@ -99,6 +128,25 @@
assertEquals(Typeface.BOLD, tp.getTypeface().getStyle());
}
+ @Test
+ public void testUpdateDrawState_withFontWeightAdjustment() {
+ StyleSpan styleSpan = new StyleSpan(Typeface.BOLD, 300);
+
+ TextPaint tp = new TextPaint();
+ Typeface tf = Typeface.defaultFromStyle(Typeface.NORMAL);
+ tp.setTypeface(tf);
+
+ assertNotNull(tp.getTypeface());
+ assertEquals(Typeface.NORMAL, tp.getTypeface().getStyle());
+
+ styleSpan.updateDrawState(tp);
+
+ assertNotNull(tp.getTypeface());
+ assertEquals(Typeface.BOLD, tp.getTypeface().getStyle());
+ assertEquals(tp.getTypeface().getWeight(), FontStyle.FONT_WEIGHT_MAX);
+ }
+
+
@Test(expected=NullPointerException.class)
public void testUpdateDrawStateNull() {
StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
diff --git a/tests/tests/text/src/android/text/style/cts/SuggestionRangeSpanTest.java b/tests/tests/text/src/android/text/style/cts/SuggestionRangeSpanTest.java
new file mode 100644
index 0000000..a83e50f
--- /dev/null
+++ b/tests/tests/text/src/android/text/style/cts/SuggestionRangeSpanTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.style.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.text.TextPaint;
+import android.text.style.SuggestionRangeSpan;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test {@link SuggestionRangeSpan}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SuggestionRangeSpanTest {
+ @Test
+ public void testConstructor() {
+ final SuggestionRangeSpan span = new SuggestionRangeSpan();
+ assertEquals(0, span.getBackgroundColor());
+ }
+
+ @Test
+ public void testSerializationDeserialization() {
+ final SuggestionRangeSpan original = new SuggestionRangeSpan();
+ original.setBackgroundColor(1);
+ Parcel parcel = null;
+ final SuggestionRangeSpan clone;
+ try {
+ parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ clone = SuggestionRangeSpan.CREATOR.createFromParcel(parcel);
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ }
+ }
+ assertEquals(original.getBackgroundColor(), clone.getBackgroundColor());
+ }
+
+ @Test
+ public void testSetAndGetBackgroundColor() {
+ final SuggestionRangeSpan span = new SuggestionRangeSpan();
+ assertEquals(0, span.getBackgroundColor());
+
+ span.setBackgroundColor(1);
+ assertEquals(1, span.getBackgroundColor());
+ }
+
+ @Test
+ public void testUpdateDrawState() {
+ final SuggestionRangeSpan span = new SuggestionRangeSpan();
+ span.setBackgroundColor(1);
+ TextPaint tp = new TextPaint();
+ span.updateDrawState(tp);
+ assertEquals(1, tp.bgColor);
+ }
+}
+
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java
index b88b7d7..6127f10 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java
@@ -52,6 +52,8 @@
private static final String TAG = "CtsTextClassifierService";
public static final String MY_PACKAGE = "android.view.textclassifier.cts";
+ public static final int DEFAULT_API_WAIT_TIMEOUT = 1_000;
+
private static final Icon ICON_RES =
Icon.createWithResource("android", android.R.drawable.btn_star);
static final Icon ICON_URI =
@@ -64,7 +66,7 @@
private final Map<String, List<TextClassificationSessionId>> mRequestSessions =
new ArrayMap<>();
- private final CountDownLatch mRequestLatch = new CountDownLatch(1);
+ private CountDownLatch mRequestLatch = new CountDownLatch(1);
/**
* Returns the TestWatcher that was used for the testing.
@@ -82,6 +84,14 @@
return Collections.unmodifiableMap(mRequestSessions);
}
+ void resetRequestLatch(int count) {
+ mRequestLatch = new CountDownLatch(count);
+ }
+
+ void awaitQuery() {
+ awaitQuery(DEFAULT_API_WAIT_TIMEOUT);
+ }
+
void awaitQuery(long timeoutMillis) {
try {
mRequestLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java
index 5f18026..bafac32 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java
@@ -42,6 +42,10 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TextClassificationTest {
@@ -174,14 +178,18 @@
@Test
public void testTextClassificationRequest() {
+ final ZonedDateTime referenceTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(1000L),
+ ZoneId.of("UTC"));
final TextClassification.Request request =
new TextClassification.Request.Builder(TEXT, START, END)
.setDefaultLocales(LOCALES)
+ .setReferenceTime(referenceTime)
.setExtras(BUNDLE)
.build();
assertEquals(LOCALES, request.getDefaultLocales());
assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
+ assertEquals(referenceTime, request.getReferenceTime());
}
@Test
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
index a95460b..a732832 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
@@ -21,19 +21,21 @@
import static org.junit.Assume.assumeTrue;
-import android.app.Instrumentation;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Icon;
+import android.os.CancellationSignal;
import android.os.SystemClock;
+import android.service.textclassifier.TextClassifierService;
+import android.view.textclassifier.ConversationActions;
+import android.view.textclassifier.SelectionEvent;
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassificationContext;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassificationSessionId;
import android.view.textclassifier.TextClassifier;
-import android.view.textclassifier.TextLanguage;
import android.view.textclassifier.TextSelection;
import androidx.core.os.BuildCompat;
@@ -50,7 +52,9 @@
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
+import java.util.Collections;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
/**
* Tests for TextClassifierService query related functions.
@@ -94,14 +98,14 @@
final CtsTextClassifierService service = mTestWatcher.getService();
// Wait a delay for the query is delivered.
- service.awaitQuery(1_000);
+ service.awaitQuery();
// Verify the request was not passed to the service.
assertThat(service.getRequestSessions()).isEmpty();
}
@Test
- public void multipleActiveSessions() throws Exception {
+ public void testMultipleActiveSessions() throws Exception {
final TextClassification.Request request =
new TextClassification.Request.Builder("Hello World", 0, 1).build();
final TextClassificationManager tcm =
@@ -113,14 +117,72 @@
tcm.createTextClassificationSession(mTextClassificationContext);
firstSession.classifyText(request);
- secondSessionSession.classifyText(request);
final CtsTextClassifierService service = mTestWatcher.getService();
- service.awaitQuery(1_000);
+ service.awaitQuery();
+
+ service.resetRequestLatch(1);
+ secondSessionSession.classifyText(request);
+ service.awaitQuery();
final List<TextClassificationSessionId> sessionIds =
service.getRequestSessions().get("onClassifyText");
assertThat(sessionIds).hasSize(2);
assertThat(sessionIds.get(0).getValue()).isNotEqualTo(sessionIds.get(1).getValue());
+
+ service.resetRequestLatch(2);
+ firstSession.destroy();
+ secondSessionSession.destroy();
+ service.awaitQuery();
+
+ final List<TextClassificationSessionId> destroyedSessionIds =
+ service.getRequestSessions().get("onDestroyTextClassificationSession");
+ assertThat(destroyedSessionIds).hasSize(2);
+ firstSession.isDestroyed();
+ secondSessionSession.isDestroyed();
+ }
+
+ private void serviceOnSuggestConversationActions(CtsTextClassifierService service)
+ throws Exception {
+ ConversationActions.Request conversationActionRequest =
+ new ConversationActions.Request.Builder(Collections.emptyList()).build();
+ // TODO: add @TestApi for TextClassificationSessionId and use it
+ SelectionEvent event =
+ SelectionEvent.createSelectionStartedEvent(
+ SelectionEvent.INVOCATION_LINK, 1);
+ final CountDownLatch onSuccessLatch = new CountDownLatch(1);
+ service.onSuggestConversationActions(event.getSessionId(),
+ conversationActionRequest, new CancellationSignal(),
+ new TextClassifierService.Callback<ConversationActions>() {
+ @Override
+ public void onFailure(CharSequence charSequence) {
+ // do nothing
+ }
+
+ @Override
+ public void onSuccess(ConversationActions o) {
+ onSuccessLatch.countDown();
+ }
+ });
+ onSuccessLatch.await();
+ final List<TextClassificationSessionId> sessionIds =
+ service.getRequestSessions().get("onSuggestConversationActions");
+ assertThat(sessionIds).hasSize(1);
+ }
+
+ @Test
+ public void testTextClassifierServiceApiCoverage() throws Exception {
+ // Implemented for API test coverage only
+ // Any method that is better tested not be called here.
+ // We already have tests for the TC APIs
+ // We however need to directly query the TextClassifierService methods in the tests for
+ // code coverage.
+ CtsTextClassifierService service = new CtsTextClassifierService();
+
+ service.onConnected();
+
+ serviceOnSuggestConversationActions(service);
+
+ service.onDisconnected();
}
@Test
diff --git a/tests/tests/time/TEST_MAPPING b/tests/tests/time/TEST_MAPPING
new file mode 100644
index 0000000..9b183e2
--- /dev/null
+++ b/tests/tests/time/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsTimeTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceConfigKeys.java b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceConfigKeys.java
index 67c0289..d2f8996 100644
--- a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceConfigKeys.java
+++ b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceConfigKeys.java
@@ -33,53 +33,15 @@
* Keys and values associated with the location_time_zone_manager.
* See also {@link LocationTimeZoneManagerShellHelper}.
*/
- final class LocationTimeZoneManager {
+ public final class LocationTimeZoneManager {
private LocationTimeZoneManager() {
// No need to instantiate.
}
- /**
- * The key for the server flag that can override the device config for whether the primary
- * location time zone provider is enabled, disabled, or (for testing) in simulation mode.
- */
- static final String KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE =
- "primary_location_time_zone_provider_mode_override";
-
- /**
- * The key for the server flag that can override the device config for whether the secondary
- * location time zone provider is enabled or disabled, or (for testing) in simulation mode.
- */
- static final String KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE =
- "secondary_location_time_zone_provider_mode_override";
-
- /**
- * The "simulated" provider mode.
- * For use with {@link #KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE} and {@link
- * #KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE}.
- */
- static final String PROVIDER_MODE_SIMULATED = "simulated";
-
- /**
- * The "disabled" provider mode (equivalent to there being no provider configured).
- * For use with {@link #KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE} and {@link
- * #KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE}.
- */
- static final String PROVIDER_MODE_DISABLED = "disabled";
-
- /**
- * The key for the server flag that can override the device config for the package name of
- * the primary provider (when enabled).
- */
- static final String KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_PACKAGE_NAME_OVERRIDE =
- "primary_location_time_zone_provider_package_name_override";
-
- /**
- * The key for the server flag that can override the device config for the package name of
- * the secondary provider (when enabled).
- */
- static final String KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_PACKAGE_NAME_OVERRIDE =
- "secondary_location_time_zone_provider_package_name_override";
+ /** The key for the setting that controls rate limiting of provider events. */
+ public static final String KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS =
+ "ltzp_event_filtering_age_threshold_millis";
}
/**
diff --git a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceConfigShellHelper.java b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceConfigShellHelper.java
index 550daf8..b128a69 100644
--- a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceConfigShellHelper.java
+++ b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceConfigShellHelper.java
@@ -38,17 +38,20 @@
public class DeviceConfigShellHelper {
/**
- * Value used with {@link #setSyncModeForTest}, {@link #setSyncDisabled(String)}.
+ * Value used with {@link #setSyncModeForTest}, {@link #getSyncDisabled()},
+ * {@link #setSyncDisabled(String)}.
*/
public static final String SYNC_DISABLED_MODE_NONE = "none";
/**
- * Value used with {@link #setSyncModeForTest}, {@link #setSyncDisabled(String)}.
+ * Value used with {@link #setSyncModeForTest}, {@link #getSyncDisabled()},
+ * {@link #setSyncDisabled(String)}.
*/
public static final String SYNC_DISABLED_MODE_UNTIL_REBOOT = "until_reboot";
/**
- * Value used with {@link #setSyncModeForTest}, {@link #setSyncDisabled(String)}.
+ * Value used with {@link #setSyncModeForTest}, {@link #getSyncDisabled()},
+ * {@link #setSyncDisabled(String)}.
*/
public static final String SYNC_DISABLED_MODE_PERSISTENT = "persistent";
@@ -64,11 +67,13 @@
}
/**
- * Executes "is_sync_disabled_for_tests". Returns {@code true} or {@code false}.
+ * Executes "get_sync_disabled_for_tests". Returns the output, expected to be one of
+ * {@link #SYNC_DISABLED_MODE_PERSISTENT}, {@link #SYNC_DISABLED_MODE_UNTIL_REBOOT} or
+ * {@link #SYNC_DISABLED_MODE_NONE}.
*/
- public boolean isSyncDisabled() throws Exception {
- String cmd = SHELL_CMD_PREFIX + "is_sync_disabled_for_tests";
- return mShellCommandExecutor.executeToBoolean(cmd);
+ public String getSyncDisabled() throws Exception {
+ String cmd = SHELL_CMD_PREFIX + "get_sync_disabled_for_tests";
+ return mShellCommandExecutor.executeToTrimmedString(cmd);
}
/**
@@ -135,7 +140,7 @@
NamespaceEntries namespaceValues = list(namespacetoSave);
savedValues.add(namespaceValues);
}
- PreTestState preTestState = new PreTestState(isSyncDisabled(), savedValues);
+ PreTestState preTestState = new PreTestState(getSyncDisabled(), savedValues);
setSyncDisabled(syncMode);
return preTestState;
}
@@ -155,8 +160,7 @@
subMap(oldEntries.keyValues, difference.entriesDiffering().keySet());
putAll(oldEntries.namespace, entriesToUpdate);
}
- setSyncDisabled(restoreState.mIsSyncDisabled
- ? SYNC_DISABLED_MODE_UNTIL_REBOOT : SYNC_DISABLED_MODE_NONE);
+ setSyncDisabled(restoreState.mSyncDisabledMode);
}
private static <X, Y> Map<X, Y> subMap(Map<X, Y> keyValues, Set<X> keySet) {
@@ -177,11 +181,11 @@
/** Opaque saved state information. */
public static class PreTestState {
- private final boolean mIsSyncDisabled;
+ private final String mSyncDisabledMode;
private final List<NamespaceEntries> mSavedValues = new ArrayList<>();
- private PreTestState(boolean isSyncDisabled, List<NamespaceEntries> values) {
- mIsSyncDisabled = isSyncDisabled;
+ private PreTestState(String syncDisabledMode, List<NamespaceEntries> values) {
+ mSyncDisabledMode = syncDisabledMode;
mSavedValues.addAll(values);
}
}
diff --git a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceShellCommandExecutor.java b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceShellCommandExecutor.java
index c999181..dc17c63 100644
--- a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceShellCommandExecutor.java
+++ b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceShellCommandExecutor.java
@@ -86,6 +86,6 @@
* Converts command line bytes to a String.
*/
protected static String parseBytesAsString(byte[] result) {
- return new String(result, 0, result.length, StandardCharsets.ISO_8859_1);
+ return new String(result, 0, result.length, StandardCharsets.UTF_8);
}
}
diff --git a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/FakeTimeZoneProviderAppShellHelper.java b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/FakeTimeZoneProviderAppShellHelper.java
new file mode 100644
index 0000000..2c99860
--- /dev/null
+++ b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/FakeTimeZoneProviderAppShellHelper.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.time.cts.shell;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A class for interacting with the fake {@link android.service.timezone.TimeZoneProviderService} in
+ * the fake TimeZoneProviderService app.
+ */
+public final class FakeTimeZoneProviderAppShellHelper {
+
+ /** The name of the app's APK. */
+ public static final String FAKE_TZPS_APP_APK = "CtsFakeTimeZoneProvidersApp.apk";
+
+ /** The package name of the app. */
+ public static final String FAKE_TZPS_APP_PACKAGE = "com.android.time.cts.fake_tzps_app";
+
+ /** The ID of the primary location time zone provider. */
+ public static final String FAKE_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ID =
+ "FakeLocationTimeZoneProviderService1";
+
+ /** The ID of the secondary location time zone provider. */
+ public static final String FAKE_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ID =
+ "FakeLocationTimeZoneProviderService2";
+
+ // The following constant values correspond to enum values from
+ // frameworks/base/core/proto/android/app/location_time_zone_manager.proto
+ public static final int PROVIDER_STATE_UNKNOWN = 0;
+ public static final int PROVIDER_STATE_INITIALIZING = 1;
+ public static final int PROVIDER_STATE_CERTAIN = 2;
+ public static final int PROVIDER_STATE_UNCERTAIN = 3;
+ public static final int PROVIDER_STATE_DISABLED = 4;
+ public static final int PROVIDER_STATE_PERM_FAILED = 5;
+ public static final int PROVIDER_STATE_DESTROYED = 6;
+
+ private static final String METHOD_GET_STATE = "get_state";
+ private static final String CALL_RESULT_KEY_GET_STATE_STATE = "state";
+ private static final String METHOD_REPORT_PERMANENT_FAILURE = "perm_fail";
+ private static final String METHOD_REPORT_UNCERTAIN = "uncertain";
+ private static final String METHOD_REPORT_SUCCESS = "success";
+ private static final String METHOD_PING = "ping";
+
+ /** A single string, comma separated, may be empty. */
+ private static final String CALL_EXTRA_KEY_SUCCESS_SUGGESTION_ZONE_IDS = "zone_ids";
+
+ private static final String SHELL_COMMAND_PREFIX = "content ";
+ private static final String AUTHORITY = "faketzpsapp ";
+
+ private final DeviceShellCommandExecutor mShellCommandExecutor;
+
+ public FakeTimeZoneProviderAppShellHelper(DeviceShellCommandExecutor shellCommandExecutor) {
+ mShellCommandExecutor = Objects.requireNonNull(shellCommandExecutor);
+ }
+
+ /**
+ * Throws an exception if the app is not installed / available within a reasonable time.
+ */
+ public void waitForInstallation() throws Exception {
+ long timeoutMs = 10000;
+ long delayUntilMillis = System.currentTimeMillis() + timeoutMs;
+ while (System.currentTimeMillis() <= delayUntilMillis) {
+ try {
+ ping();
+ return;
+ } catch (AssertionError e) {
+ // Not present yet.
+ }
+ Thread.sleep(100);
+ }
+ fail("Installation did not happen in time");
+ }
+
+ public FakeTimeZoneProviderShellHelper getPrimaryLocationProviderHelper() {
+ return getProviderHelper(FAKE_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ID);
+ }
+
+ public FakeTimeZoneProviderShellHelper getSecondaryLocationProviderHelper() {
+ return getProviderHelper(FAKE_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ID);
+ }
+
+ private FakeTimeZoneProviderShellHelper getProviderHelper(String providerId) {
+ return new FakeTimeZoneProviderShellHelper(providerId);
+ }
+
+ /**
+ * A helper for interacting with a specific {@link
+ * android.service.timezone.TimeZoneProviderService}.
+ */
+ public final class FakeTimeZoneProviderShellHelper {
+
+ private final String mProviderId;
+
+ private FakeTimeZoneProviderShellHelper(String providerId) {
+ mProviderId = Objects.requireNonNull(providerId);
+ }
+
+ public void reportUncertain() throws Exception {
+ executeContentProviderCall(mProviderId, METHOD_REPORT_UNCERTAIN, null);
+ }
+
+ public void reportPermanentFailure() throws Exception {
+ executeContentProviderCall(mProviderId, METHOD_REPORT_PERMANENT_FAILURE, null);
+ }
+
+ public void reportSuccess(String zoneId) throws Exception {
+ reportSuccess(Collections.singletonList(zoneId));
+ }
+
+ public void reportSuccess(List<String> zoneIds) throws Exception {
+ String zoneIdsExtra = String.join(",", zoneIds);
+ Map<String, String> extras = new HashMap<>();
+ extras.put(CALL_EXTRA_KEY_SUCCESS_SUGGESTION_ZONE_IDS, zoneIdsExtra);
+
+ executeContentProviderCall(mProviderId, METHOD_REPORT_SUCCESS, extras);
+ }
+
+ public int getState() throws Exception {
+ String stateResult = executeContentProviderCall(mProviderId, METHOD_GET_STATE, null);
+ Pattern pattern = Pattern.compile(".*" + CALL_RESULT_KEY_GET_STATE_STATE + "=(.).*");
+ Matcher matcher = pattern.matcher(stateResult);
+ if (!matcher.matches()) {
+ throw new RuntimeException("Unknown result format: " + stateResult);
+ }
+ return Integer.parseInt(matcher.group(1));
+ }
+
+ public void assertCurrentState(int expectedState) throws Exception {
+ assertEquals(expectedState, getState());
+ }
+
+ public boolean exists() throws Exception {
+ try {
+ getState();
+ return true;
+ } catch (AssertionError e) {
+ return false;
+ }
+ }
+
+ public void assertCreated() throws Exception {
+ assertTrue(exists());
+ }
+
+ public void assertNotCreated() throws Exception {
+ assertFalse(exists());
+ }
+ }
+
+ private void ping() throws Exception {
+ String cmd = String.format("call --uri content://%s --method %s", AUTHORITY, METHOD_PING);
+ mShellCommandExecutor.executeToTrimmedString(SHELL_COMMAND_PREFIX + cmd);
+ }
+
+ private String executeContentProviderCall(
+ String providerId, String method, Map<String, String> extras) throws Exception {
+ String cmd = String.format("call --uri content://%s --method %s --arg %s",
+ AUTHORITY, method, providerId);
+ if (extras != null) {
+ for (Map.Entry<String, String> entry : extras.entrySet()) {
+ cmd += String.format(" --extra %s:s:%s", entry.getKey(), entry.getValue());
+ }
+ }
+ return mShellCommandExecutor.executeToTrimmedString(SHELL_COMMAND_PREFIX + cmd);
+ }
+}
diff --git a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/LocationTimeZoneManagerShellHelper.java b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/LocationTimeZoneManagerShellHelper.java
index 416443c..2417808 100644
--- a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/LocationTimeZoneManagerShellHelper.java
+++ b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/LocationTimeZoneManagerShellHelper.java
@@ -15,10 +15,6 @@
*/
package android.app.time.cts.shell;
-import static android.app.time.cts.shell.DeviceConfigKeys.LocationTimeZoneManager.KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE;
-import static android.app.time.cts.shell.DeviceConfigKeys.LocationTimeZoneManager.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE;
-import static android.app.time.cts.shell.DeviceConfigKeys.NAMESPACE_SYSTEM_TIME;
-
import static org.junit.Assume.assumeTrue;
import java.io.BufferedReader;
@@ -30,47 +26,6 @@
* command-line interface.
*/
public class LocationTimeZoneManagerShellHelper {
- /**
- * The index of the primary location time zone provider, used for shell commands.
- */
- public static final int PRIMARY_PROVIDER_INDEX = 0;
-
- /**
- * The index of the secondary location time zone provider, used for shell commands.
- */
- public static final int SECONDARY_PROVIDER_INDEX = 1;
-
- /**
- * The "disabled" provider mode (equivalent to there being no provider configured).
- */
- public static final String PROVIDER_MODE_DISABLED =
- DeviceConfigKeys.LocationTimeZoneManager.PROVIDER_MODE_DISABLED;
-
- /**
- * The "simulated" provider mode.
- */
- public static final String PROVIDER_MODE_SIMULATED =
- DeviceConfigKeys.LocationTimeZoneManager.PROVIDER_MODE_SIMULATED;
-
- /**
- * Simulated provider test command that simulates the bind succeeding.
- */
- public static final String SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND = "on_bind";
-
- /**
- * Simulated provider test command that simulates the provider reporting uncertainty.
- */
- public static final String SIMULATED_PROVIDER_TEST_COMMAND_UNCERTAIN = "uncertain";
-
- /**
- * Simulated provider test command that simulates a successful time zone detection.
- */
- public static final String SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS = "success";
-
- /**
- * Argument for {@link #SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS} to specify TZDB time zone IDs.
- */
- public static final String SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ = "tz";
/**
* The name of the service for shell commands.
@@ -88,10 +43,10 @@
private static final String SHELL_COMMAND_STOP = "stop";
/**
- * A shell command that tells the service to record state information during tests. The next
- * argument value is "true" or "false".
+ * A shell command that clears recorded provider state information during tests.
*/
- private static final String SHELL_COMMAND_RECORD_PROVIDER_STATES = "record_provider_states";
+ private static final String SHELL_COMMAND_CLEAR_RECORDED_PROVIDER_STATES =
+ "clear_recorded_provider_states";
/**
* A shell command that tells the service to dump its current state.
@@ -103,20 +58,22 @@
*/
private static final String DUMP_STATE_OPTION_PROTO = "proto";
+ /** A shell command that starts the location_time_zone_manager with named test providers. */
+ public static final String SHELL_COMMAND_START_WITH_TEST_PROVIDERS =
+ "start_with_test_providers";
+
/**
- * A shell command that sends test commands to a provider
+ * The token that can be passed to {@link #SHELL_COMMAND_START_WITH_TEST_PROVIDERS} to indicate
+ * there is no provider.
*/
- private static final String SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND =
- "send_provider_test_command";
+ public static final String NULL_PACKAGE_NAME_TOKEN = "@null";
private static final String SHELL_CMD_PREFIX = "cmd " + SERVICE_NAME + " ";
private final DeviceShellCommandExecutor mShellCommandExecutor;
- private final DeviceConfigShellHelper mDeviceConfigShellHelper;
public LocationTimeZoneManagerShellHelper(DeviceShellCommandExecutor shellCommandExecutor) {
mShellCommandExecutor = Objects.requireNonNull(shellCommandExecutor);
- mDeviceConfigShellHelper = new DeviceConfigShellHelper(shellCommandExecutor);
}
/**
@@ -156,66 +113,38 @@
mShellCommandExecutor.executeToTrimmedString(SHELL_CMD_PREFIX + SHELL_COMMAND_STOP);
}
- /** Executes "record_provider_states". */
- public void recordProviderStates(boolean enabled) throws Exception {
- String cmd = String.format("%s %s", SHELL_COMMAND_RECORD_PROVIDER_STATES, enabled);
+ /** Executes "clear_recorded_provider_states". */
+ public void clearRecordedProviderStates() throws Exception {
+ String cmd = SHELL_COMMAND_CLEAR_RECORDED_PROVIDER_STATES;
mShellCommandExecutor.executeToTrimmedString(SHELL_CMD_PREFIX + cmd);
}
- /** Executes "dump_state". */
+ /**
+ * Executes "dump_state". Raw proto bytes are returned as host protos tend to use "full" proto,
+ * device protos use "lite".
+ **/
public byte[] dumpState() throws Exception {
String cmd = String.format("%s --%s", SHELL_COMMAND_DUMP_STATE, DUMP_STATE_OPTION_PROTO);
return mShellCommandExecutor.executeToBytes(SHELL_CMD_PREFIX + cmd);
}
- /** Modifies a provider's mode using "device_config" commands. */
- public void setProviderModeOverride(int providerIndex, String mode) throws Exception {
- String deviceConfigKey;
- if (providerIndex == PRIMARY_PROVIDER_INDEX) {
- deviceConfigKey = KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE;
- } else {
- deviceConfigKey = KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE;
- }
-
- if (mode == null) {
- mDeviceConfigShellHelper.delete(NAMESPACE_SYSTEM_TIME, deviceConfigKey);
- } else {
- mDeviceConfigShellHelper.put(NAMESPACE_SYSTEM_TIME, deviceConfigKey, mode);
- }
- }
-
- /**
- * Simulates a provider successfully binding using the "send_provider_test_command" command.
- */
- public void simulateProviderBind(int providerIndex) throws Exception {
- sendProviderTestCommand(providerIndex, SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND);
- }
-
- /**
- * Simulates a provider generating an uncertain report using the "send_provider_test_command"
- * command.
- */
- public void simulateProviderUncertain(int providerIndex) throws Exception {
- sendProviderTestCommand(providerIndex, SIMULATED_PROVIDER_TEST_COMMAND_UNCERTAIN);
- }
-
- /**
- * Simulates a provider generating a suggestion using the "send_provider_test_command" command.
- */
- public void simulateProviderSuggestion(int providerIndex, String... zoneIds)
+ /** Executes "start_with_test_providers". */
+ public void startWithTestProviders(String testPrimaryLocationTimeZoneProviderPackageName,
+ String testSecondaryLocationTimeZoneProviderPackageName, boolean recordProviderStates)
throws Exception {
- String timeZoneIds = String.join("&", zoneIds);
- String testCommand = String.format("%s %s=string_array:%s",
- SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS,
- SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ,
- timeZoneIds);
- sendProviderTestCommand(providerIndex, testCommand);
+ testPrimaryLocationTimeZoneProviderPackageName =
+ replaceNullPackageNameWithToken(testPrimaryLocationTimeZoneProviderPackageName);
+ testSecondaryLocationTimeZoneProviderPackageName =
+ replaceNullPackageNameWithToken(testSecondaryLocationTimeZoneProviderPackageName);
+ String cmd = String.format("%s %s %s %s",
+ SHELL_COMMAND_START_WITH_TEST_PROVIDERS,
+ testPrimaryLocationTimeZoneProviderPackageName,
+ testSecondaryLocationTimeZoneProviderPackageName,
+ recordProviderStates);
+ mShellCommandExecutor.executeToBytes(SHELL_CMD_PREFIX + cmd);
}
- /** Executes "send_provider_test_command". */
- private void sendProviderTestCommand(int providerIndex, String testCommand) throws Exception {
- String cmd = String.format("%s %s %s",
- SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND, providerIndex, testCommand);
- mShellCommandExecutor.executeToTrimmedString(SHELL_CMD_PREFIX + cmd);
+ private static String replaceNullPackageNameWithToken(String packageName) {
+ return packageName == null ? NULL_PACKAGE_NAME_TOKEN : packageName;
}
}
diff --git a/tests/tests/time/shell_utils/device/android/app/time/cts/shell/device/InstrumentationShellCommandExecutor.java b/tests/tests/time/shell_utils/device/android/app/time/cts/shell/device/InstrumentationShellCommandExecutor.java
index 5d7d7ac..2ae437f 100644
--- a/tests/tests/time/shell_utils/device/android/app/time/cts/shell/device/InstrumentationShellCommandExecutor.java
+++ b/tests/tests/time/shell_utils/device/android/app/time/cts/shell/device/InstrumentationShellCommandExecutor.java
@@ -48,7 +48,7 @@
byte[] stdOut = readBytesAndClose(parcelFileDescriptors[0]);
byte[] stdErr = readBytesAndClose(parcelFileDescriptors[2]);
if (stdErr.length > 0) {
- fail("Command \'" + command + "\'produced stderr: "
+ fail("Command \'" + command + "\' produced stderr: "
+ parseBytesAsString(stdErr).trim());
}
return stdOut;
diff --git a/tests/tests/time/shell_utils/host/android/app/time/cts/shell/host/HostShellCommandExecutor.java b/tests/tests/time/shell_utils/host/android/app/time/cts/shell/host/HostShellCommandExecutor.java
index 5356617..85b41aa 100644
--- a/tests/tests/time/shell_utils/host/android/app/time/cts/shell/host/HostShellCommandExecutor.java
+++ b/tests/tests/time/shell_utils/host/android/app/time/cts/shell/host/HostShellCommandExecutor.java
@@ -15,17 +15,24 @@
*/
package android.app.time.cts.shell.host;
+import static org.junit.Assert.fail;
+
import android.app.time.cts.shell.DeviceShellCommandExecutor;
import androidx.annotation.NonNull;
-import com.android.tradefed.device.CollectingByteOutputReceiver;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.util.CommandResult;
+import java.io.ByteArrayOutputStream;
import java.util.Objects;
+import java.util.concurrent.TimeUnit;
public final class HostShellCommandExecutor extends DeviceShellCommandExecutor {
+ private static final long MAX_TIMEOUT_FOR_COMMAND_MILLIS = 2 * 60 * 1000;
+ private static final int RETRY_ATTEMPTS = 1;
+
private final ITestDevice mDevice;
public HostShellCommandExecutor(@NonNull ITestDevice device) {
@@ -35,9 +42,17 @@
@Override
@NonNull
protected byte[] executeToBytesInternal(String command) throws Exception {
- CollectingByteOutputReceiver bytesReceiver = new CollectingByteOutputReceiver();
- mDevice.executeShellCommand(command, bytesReceiver);
- return bytesReceiver.getOutput();
+ ByteArrayOutputStream stdOutBytesReceiver = new ByteArrayOutputStream();
+ ByteArrayOutputStream stdErrBytesReceiver = new ByteArrayOutputStream();
+ CommandResult result = mDevice.executeShellV2Command(
+ command, /*pipeAsInput=*/null, stdOutBytesReceiver, stdErrBytesReceiver,
+ MAX_TIMEOUT_FOR_COMMAND_MILLIS, TimeUnit.MILLISECONDS, RETRY_ATTEMPTS);
+ if (result.getExitCode() != 0 || stdErrBytesReceiver.size() > 0) {
+ fail("Command \'" + command + "\' produced exitCode=" + result.getExitCode()
+ + " and stderr="
+ + parseBytesAsString(stdErrBytesReceiver.toByteArray()).trim());
+ }
+ return stdOutBytesReceiver.toByteArray();
}
@Override
diff --git a/tests/tests/toast/AndroidTest.xml b/tests/tests/toast/AndroidTest.xml
index 4195e03..0a26b42 100644
--- a/tests/tests/toast/AndroidTest.xml
+++ b/tests/tests/toast/AndroidTest.xml
@@ -16,6 +16,7 @@
<configuration description="Config for Toast test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
diff --git a/tests/tests/toastlegacy/AndroidTest.xml b/tests/tests/toastlegacy/AndroidTest.xml
index 95d5a02..2d6c4c6 100644
--- a/tests/tests/toastlegacy/AndroidTest.xml
+++ b/tests/tests/toastlegacy/AndroidTest.xml
@@ -16,6 +16,7 @@
<configuration description="Config for Toast legacy test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
diff --git a/tests/tests/tv/Android.bp b/tests/tests/tv/Android.bp
index f9f2ee4..438393f 100644
--- a/tests/tests/tv/Android.bp
+++ b/tests/tests/tv/Android.bp
@@ -18,7 +18,7 @@
android_library {
name: "CtsTvTestCases_lib",
- srcs: ["src/**/*.java"],
+ srcs: ["src/**/*.java", "src/**/*.aidl"],
libs: [
"platform-test-annotations",
"android.test.runner",
diff --git a/tests/tests/tv/AndroidManifest.xml b/tests/tests/tv/AndroidManifest.xml
index 6f1b399..b79e354 100644
--- a/tests/tests/tv/AndroidManifest.xml
+++ b/tests/tests/tv/AndroidManifest.xml
@@ -46,6 +46,14 @@
</intent-filter>
</activity>
+ <service android:name="android.media.tv.tuner.cts.SharedFilterTestService"
+ android:process=":SharedFilterTestService"
+ android:exported="true"/>
+
+ <service android:name="android.media.tv.tuner.cts.TunerResourceTestService"
+ android:process=":TunerResourceTestService"
+ android:exported="true"/>
+
<service android:name="android.media.tv.cts.StubTunerTvInputService"
android:permission="android.permission.BIND_TV_INPUT"
android:label="TV input stub"
@@ -128,6 +136,15 @@
android:resource="@xml/stub_tv_input_service"/>
</service>
+ <service android:name="android.media.tv.interactive.cts.StubTvIAppService"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.media.tv.interactive.TvIAppService"/>
+ </intent-filter>
+ <meta-data android:name="android.media.tv.interactive.app"
+ android:resource="@xml/stub_tv_iapp_service"/>
+ </service>
+
<activity android:name="android.media.tv.cts.TvViewStubActivity"
android:exported="true">
<intent-filter>
@@ -142,6 +159,14 @@
<category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
</intent-filter>
</activity>
+
+ <activity android:name="android.media.tv.interactive.cts.TvInteractiveAppViewStubActivity"
+ 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>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/tv/res/layout/tviappview_layout.xml b/tests/tests/tv/res/layout/tviappview_layout.xml
new file mode 100644
index 0000000..6fc8cf7
--- /dev/null
+++ b/tests/tests/tv/res/layout/tviappview_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <android.media.tv.interactive.TvInteractiveAppView
+ android:id="@+id/tviappview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+</LinearLayout>
diff --git a/tests/tests/tv/res/values/arrays.xml b/tests/tests/tv/res/values/arrays.xml
new file mode 100644
index 0000000..3794d0b
--- /dev/null
+++ b/tests/tests/tv/res/values/arrays.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string-array name="sub_iapp_service_types">
+ <item>ginga</item>
+ <item>hbbtv</item>
+ </string-array>
+</resources>
diff --git a/tests/tests/tv/res/xml/stub_tv_iapp_service.xml b/tests/tests/tv/res/xml/stub_tv_iapp_service.xml
new file mode 100644
index 0000000..bb9747a
--- /dev/null
+++ b/tests/tests/tv/res/xml/stub_tv_iapp_service.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<tv-iapp xmlns:android="http://schemas.android.com/apk/res/android"
+ android:supportedTypes="@array/sub_iapp_service_types"
+ android:canPauseRecording="true" />
diff --git a/tests/tests/tv/src/android/media/tv/cts/StubTvInputService.java b/tests/tests/tv/src/android/media/tv/cts/StubTvInputService.java
index b70dc62..33648cb 100644
--- a/tests/tests/tv/src/android/media/tv/cts/StubTvInputService.java
+++ b/tests/tests/tv/src/android/media/tv/cts/StubTvInputService.java
@@ -30,7 +30,7 @@
return new StubSessionImpl(this);
}
- private static class StubSessionImpl extends Session {
+ public static class StubSessionImpl extends Session {
StubSessionImpl(Context context) {
super(context);
}
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java b/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java
index 5345e42..5134dd3 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java
@@ -17,6 +17,8 @@
package android.media.tv.cts;
import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
@@ -36,14 +38,18 @@
import android.media.tv.TvInputManager;
import android.media.tv.TvInputManager.Hardware;
import android.media.tv.TvInputManager.HardwareCallback;
+import android.media.tv.TvInputManager.Session;
+import android.media.tv.TvInputManager.SessionCallback;
import android.media.tv.TvInputService;
import android.media.tv.TvStreamConfig;
import android.media.tv.TvView;
+import android.media.tv.tunerresourcemanager.TunerResourceManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.test.ActivityInstrumentationTestCase2;
import android.tv.cts.R;
@@ -66,6 +72,7 @@
public class TvInputManagerTest extends ActivityInstrumentationTestCase2<TvViewStubActivity> {
/** The maximum time to wait for an operation. */
private static final long TIME_OUT_MS = 15000L;
+ private static final int PRIORITY_HINT_USE_CASE_TYPE_INVALID = 1000;
private static final int DUMMY_DEVICE_ID = Integer.MAX_VALUE;
private static final String[] VALID_TV_INPUT_SERVICES = {
@@ -654,6 +661,61 @@
}
}
+ public void testGetClientPriority() {
+ if (!Utils.hasTvInputFramework(getActivity())) {
+ return;
+ }
+
+ // Use the test api to get priorities in tunerResourceManagerUseCaseConfig.xml
+ TunerResourceManager trm = (TunerResourceManager) getActivity()
+ .getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
+ int fgLivePriority = trm.getConfigPriority(TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE,
+ true);
+ int bgLivePriority = trm.getConfigPriority(TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE,
+ false);
+ int fgDefaultPriority = trm.getConfigPriority(PRIORITY_HINT_USE_CASE_TYPE_INVALID, true);
+ int bgDefaultPriority = trm.getConfigPriority(PRIORITY_HINT_USE_CASE_TYPE_INVALID, false);
+ boolean isForeground = checkIsForeground(android.os.Process.myPid());
+
+ int priority = mManager.getClientPriority(TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE,
+ null);
+ assertTrue(priority == (isForeground ? fgLivePriority : bgLivePriority));
+
+ priority = mManager.getClientPriority(
+ PRIORITY_HINT_USE_CASE_TYPE_INVALID /* invalid use case type */,
+ null);
+ assertTrue(priority == (isForeground ? fgDefaultPriority : bgDefaultPriority));
+
+ Handler handler = new Handler(Looper.getMainLooper());
+ final SessionCallback sessionCallback = new SessionCallback();
+ mManager.createSession(mStubId, sessionCallback, handler);
+ PollingCheck.waitFor(TIME_OUT_MS, () -> sessionCallback.getSession() != null);
+ Session session = sessionCallback.getSession();
+ String sessionId = StubTvInputService2.getSessionId();
+ assertNotNull(sessionId);
+
+ priority = mManager.getClientPriority(
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, sessionId /* valid sessionId */);
+ assertTrue(priority == (isForeground ? fgLivePriority : bgLivePriority));
+
+ priority = mManager.getClientPriority(
+ PRIORITY_HINT_USE_CASE_TYPE_INVALID /* invalid use case type */,
+ sessionId /* valid sessionId */);
+ assertTrue(priority == (isForeground ? fgDefaultPriority : fgDefaultPriority));
+
+ session.release();
+ PollingCheck.waitFor(TIME_OUT_MS, () -> StubTvInputService2.getSessionId() == null);
+
+ priority = mManager.getClientPriority(
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, sessionId /* invalid sessionId */);
+ assertTrue(priority == bgLivePriority);
+
+ priority = mManager.getClientPriority(
+ PRIORITY_HINT_USE_CASE_TYPE_INVALID /* invalid use case type */,
+ sessionId /* invalid sessionId */);
+ assertTrue(priority == bgDefaultPriority);
+ }
+
private static class LoggingCallback extends TvInputManager.TvInputCallback {
private final List<String> mAddedInputs = new ArrayList<>();
private final List<String> mRemovedInputs = new ArrayList<>();
@@ -704,9 +766,27 @@
}
public static class StubTvInputService2 extends StubTvInputService {
+ static String sTvInputSessionId;
+
+ public static String getSessionId() {
+ return sTvInputSessionId;
+ }
+
@Override
- public Session onCreateSession(String inputId) {
- return null;
+ public Session onCreateSession(String inputId, String tvInputSessionId) {
+ sTvInputSessionId = tvInputSessionId;
+ return new StubSessionImpl2(this);
+ }
+
+ public static class StubSessionImpl2 extends StubTvInputService.StubSessionImpl {
+ StubSessionImpl2(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onRelease() {
+ sTvInputSessionId = null;
+ }
}
}
@@ -784,4 +864,33 @@
r.run();
}
}
+
+ private class SessionCallback extends TvInputManager.SessionCallback {
+ private TvInputManager.Session mSession;
+
+ public TvInputManager.Session getSession() {
+ return mSession;
+ }
+
+ @Override
+ public void onSessionCreated(TvInputManager.Session session) {
+ mSession = session;
+ }
+ }
+
+ private boolean checkIsForeground(int pid) {
+ ActivityManager am = (ActivityManager) getActivity()
+ .getSystemService(Context.ACTIVITY_SERVICE);
+ List<RunningAppProcessInfo> appProcesses = am.getRunningAppProcesses();
+ if (appProcesses == null) {
+ return false;
+ }
+ for (RunningAppProcessInfo appProcess : appProcesses) {
+ if (appProcess.pid == pid
+ && appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/tests/tests/tv/src/android/media/tv/interactive/cts/StubTvIAppService.java b/tests/tests/tv/src/android/media/tv/interactive/cts/StubTvIAppService.java
new file mode 100644
index 0000000..fc11f2c
--- /dev/null
+++ b/tests/tests/tv/src/android/media/tv/interactive/cts/StubTvIAppService.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.interactive.cts;
+
+import android.content.Context;
+import android.media.tv.interactive.TvIAppManager;
+import android.media.tv.interactive.TvIAppService;
+import android.view.KeyEvent;
+import android.view.Surface;
+
+/**
+ * Stub implementation of (@link android.media.tv.interactive.TvIAppService}.
+ */
+public class StubTvIAppService extends TvIAppService {
+ @Override
+ public Session onCreateSession(String iAppServiceId, int type) {
+ Session session = new StubSessionImpl(this);
+ return session;
+ }
+
+ private static class StubSessionImpl extends Session {
+ StubSessionImpl(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStartInteractiveApp() {
+ notifySessionStateChanged(TvIAppManager.TV_INTERACTIVE_APP_RTE_STATE_READY);
+ }
+
+ @Override
+ public void onRelease() {
+ }
+
+ @Override
+ public boolean onSetSurface(Surface surface) {
+ return false;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
+ return false;
+ }
+ }
+}
diff --git a/tests/tests/tv/src/android/media/tv/interactive/cts/TvInteractiveAppViewStubActivity.java b/tests/tests/tv/src/android/media/tv/interactive/cts/TvInteractiveAppViewStubActivity.java
new file mode 100644
index 0000000..5d4a96a
--- /dev/null
+++ b/tests/tests/tv/src/android/media/tv/interactive/cts/TvInteractiveAppViewStubActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.interactive.cts;
+
+import android.app.Activity;
+import android.media.tv.interactive.TvInteractiveAppView;
+import android.os.Bundle;
+import android.tv.cts.R;
+
+public class TvInteractiveAppViewStubActivity extends Activity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.tviappview_layout);
+ }
+
+ public TvInteractiveAppView getTvInteractiveAppView() {
+ return findViewById(R.id.tviappview);
+ }
+}
diff --git a/tests/tests/tv/src/android/media/tv/interactive/cts/TvInteractiveAppViewTest.java b/tests/tests/tv/src/android/media/tv/interactive/cts/TvInteractiveAppViewTest.java
new file mode 100644
index 0000000..2bcf440
--- /dev/null
+++ b/tests/tests/tv/src/android/media/tv/interactive/cts/TvInteractiveAppViewTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.interactive.cts;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.tv.interactive.TvIAppManager;
+import android.media.tv.interactive.TvInteractiveAppInfo;
+import android.media.tv.interactive.TvInteractiveAppView;
+import android.os.ConditionVariable;
+import android.tv.cts.R;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.RequiredFeatureRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Test {@link android.media.tv.interactive.TvInteractiveAppView}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class TvInteractiveAppViewTest {
+ private static final long TIME_OUT_MS = 20000L;
+
+ private Instrumentation mInstrumentation;
+ private ActivityScenario<TvInteractiveAppViewStubActivity> mActivityScenario;
+ private TvInteractiveAppViewStubActivity mActivity;
+ private TvInteractiveAppView mTvInteractiveAppView;
+ private TvIAppManager mManager;
+ private TvInteractiveAppInfo mStubInfo;
+
+ @Rule
+ public RequiredFeatureRule featureRule = new RequiredFeatureRule(
+ PackageManager.FEATURE_LIVE_TV);
+
+ private final MockCallback mCallback = new MockCallback();
+
+ public static class MockCallback extends TvInteractiveAppView.TvInteractiveAppCallback {
+ private String mIAppServiceId;
+ private int mState = -1;
+
+ @Override
+ public void onSessionStateChanged(String iAppServiceId, int state) {
+ mIAppServiceId = iAppServiceId;
+ mState = state;
+ }
+ }
+
+ private TvInteractiveAppView findTvInteractiveAppViewById(int id) {
+ return (TvInteractiveAppView) mActivity.findViewById(id);
+ }
+
+ private void runTestOnUiThread(final Runnable r) throws Throwable {
+ final Throwable[] exceptions = new Throwable[1];
+ mInstrumentation.runOnMainSync(new Runnable() {
+ public void run() {
+ try {
+ r.run();
+ } catch (Throwable throwable) {
+ exceptions[0] = throwable;
+ }
+ }
+ });
+ if (exceptions[0] != null) {
+ throw exceptions[0];
+ }
+ }
+
+ private Executor getExecutor() {
+ return Runnable::run;
+ }
+
+ @Before
+ public void setUp() throws Throwable {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClass(
+ mInstrumentation.getTargetContext(), TvInteractiveAppViewStubActivity.class);
+
+ // DO NOT use ActivityScenario.launch(Class), which can cause ActivityNotFoundException
+ // related to BootstrapActivity.
+ mActivityScenario = ActivityScenario.launch(intent);
+ ConditionVariable activityReferenceObtained = new ConditionVariable();
+ mActivityScenario.onActivity(activity -> {
+ mActivity = activity;
+ activityReferenceObtained.open();
+ });
+ activityReferenceObtained.block(TIME_OUT_MS);
+
+ assertNotNull("Failed to acquire activity reference.", mActivity);
+ mTvInteractiveAppView = findTvInteractiveAppViewById(R.id.tviappview);
+ assertNotNull("Failed to find TvInteractiveAppView.", mTvInteractiveAppView);
+
+ mManager = (TvIAppManager) mActivity.getSystemService(Context.TV_IAPP_SERVICE);
+ assertNotNull("Failed to get TvIAppManager.", mManager);
+
+ for (TvInteractiveAppInfo info : mManager.getTvInteractiveAppServiceList()) {
+ if (info.getServiceInfo().name.equals(StubTvIAppService.class.getName())) {
+ mStubInfo = info;
+ }
+ }
+ assertNotNull(mStubInfo);
+ mTvInteractiveAppView.setCallback(mCallback, getExecutor());
+ }
+
+ @After
+ public void tearDown() throws Throwable {
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ mTvInteractiveAppView.reset();
+ }
+ });
+ mInstrumentation.waitForIdleSync();
+ mActivity = null;
+ mActivityScenario.close();
+ }
+
+ @Test
+ public void testConstructor() throws Throwable {
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ new TvInteractiveAppView(mActivity);
+ new TvInteractiveAppView(mActivity, null);
+ new TvInteractiveAppView(mActivity, null, 0);
+ }
+ });
+ }
+
+ @Test
+ public void testStartInteractiveApp() throws Throwable {
+ mTvInteractiveAppView.prepareInteractiveApp(mStubInfo.getId(), 1);
+ mInstrumentation.waitForIdleSync();
+ new PollingCheck(TIME_OUT_MS) {
+ @Override
+ protected boolean check() {
+ return mTvInteractiveAppView.getInteractiveAppSession() != null;
+ }
+ }.run();
+ mTvInteractiveAppView.startInteractiveApp();
+ new PollingCheck(TIME_OUT_MS) {
+ @Override
+ protected boolean check() {
+ return mCallback.mIAppServiceId == mStubInfo.getId()
+ && mCallback.mState == TvIAppManager.TV_INTERACTIVE_APP_RTE_STATE_READY;
+ }
+ }.run();
+ }
+}
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/ISharedFilterTestServer.aidl b/tests/tests/tv/src/android/media/tv/tuner/cts/ISharedFilterTestServer.aidl
new file mode 100644
index 0000000..2bf73a4
--- /dev/null
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/ISharedFilterTestServer.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.cts;
+
+interface ISharedFilterTestServer {
+ String acquireSharedFilterToken();
+ void closeFilter();
+ void freeSharedFilterToken(String token);
+ boolean verifySharedFilter(String token);
+}
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/ITunerResourceTestServer.aidl b/tests/tests/tv/src/android/media/tv/tuner/cts/ITunerResourceTestServer.aidl
new file mode 100644
index 0000000..199f0c0
--- /dev/null
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/ITunerResourceTestServer.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.cts;
+
+interface ITunerResourceTestServer {
+ void createTuner(int useCase);
+ int tune(int frontendIndex);
+ oneway void tuneAsync(int frontendIndex);
+ void closeTuner();
+ boolean verifyTunerIsNull();
+}
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/SharedFilterTestService.java b/tests/tests/tv/src/android/media/tv/tuner/cts/SharedFilterTestService.java
new file mode 100644
index 0000000..1ce51e3
--- /dev/null
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/SharedFilterTestService.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.cts;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.media.tv.tuner.Tuner;
+import android.media.tv.tuner.cts.ISharedFilterTestServer;
+import android.media.tv.tuner.filter.Filter;
+import android.media.tv.tuner.filter.FilterCallback;
+import android.media.tv.tuner.filter.FilterEvent;
+import android.media.tv.tuner.filter.SharedFilter;
+import android.media.tv.tuner.filter.SharedFilterCallback;
+import android.media.tv.tuner.frontend.FrontendInfo;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class SharedFilterTestService extends Service {
+ private static final String TAG = "SharedFilterTestService";
+ private Context mContext = null;
+ private Tuner mTuner = null;
+ private Filter mFilter = null;
+ private boolean mTuning = false;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ mContext = this;
+ mTuner = new Tuner(mContext, null, 100);
+ return new SharedFilterTestServer();
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ mTuner.close();
+ mTuner = null;
+ return false;
+ }
+
+ private class SharedFilterTestServer extends ISharedFilterTestServer.Stub {
+ @Override
+ public String acquireSharedFilterToken() {
+ mFilter = TunerTest.createTsSectionFilter(
+ mTuner, getExecutor(), getFilterCallback());
+
+ // Tune a frontend before start the filter
+ List<FrontendInfo> infos = mTuner.getAvailableFrontendInfos();
+ mTuner.tune(TunerTest.createFrontendSettings(infos.get(0)));
+ mTuning = true;
+
+ return mFilter.acquireSharedFilterToken();
+ }
+
+ @Override
+ public void closeFilter() {
+ if (mTuning) {
+ mTuner.cancelTuning();
+ mTuning = false;
+ }
+ mFilter.close();
+ mFilter = null;
+ }
+
+ @Override
+ public void freeSharedFilterToken(String token) {
+ if (mTuning) {
+ mTuner.cancelTuning();
+ mTuning = false;
+ }
+ mFilter.freeSharedFilterToken(token);
+ }
+
+ @Override
+ public boolean verifySharedFilter(String token) {
+ SharedFilter f = Tuner.openSharedFilter(
+ mContext, token, getExecutor(), getSharedFilterCallback());
+ if (f == null) {
+ Log.e(TAG, "SharedFilter is null");
+ return false;
+ }
+ if (f.start() != Tuner.RESULT_SUCCESS) {
+ f = null;
+ Log.e(TAG, "Failed to start SharedFilter");
+ return false;
+ }
+ if (f.flush() != Tuner.RESULT_SUCCESS) {
+ f.close();
+ f = null;
+ Log.e(TAG, "Failed to flush SharedFilter");
+ return false;
+ }
+ f.read(new byte[3], 0, 3);
+ if (f.stop() != Tuner.RESULT_SUCCESS) {
+ f.close();
+ f = null;
+ Log.e(TAG, "Failed to stop SharedFilter");
+ return false;
+ }
+ f.close();
+ f = null;
+ return true;
+ }
+ }
+
+ private FilterCallback getFilterCallback() {
+ return new FilterCallback() {
+ @Override
+ public void onFilterEvent(Filter filter, FilterEvent[] events) {}
+ @Override
+ public void onFilterStatusChanged(Filter filter, int status) {}
+ };
+ }
+
+ private SharedFilterCallback getSharedFilterCallback() {
+ return new SharedFilterCallback() {
+ @Override
+ public void onFilterEvent(SharedFilter filter, FilterEvent[] events) {}
+ @Override
+ public void onFilterStatusChanged(SharedFilter filter, int status) {}
+ };
+ }
+
+ private Executor getExecutor() { return Runnable::run; }
+}
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerDvrTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerDvrTest.java
index 834acad..8d47bb6 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerDvrTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerDvrTest.java
@@ -160,6 +160,13 @@
d.flush();
assertEquals(3, d.read(3));
assertEquals(3, d.read(new byte[3], 0, 3));
+ assertEquals(0, d.seek(0));
+ assertEquals(3, d.read(3));
+ assertEquals(3, d.read(new byte[3], 0, 3));
+ assertEquals(5, d.seek(5));
+ assertEquals(2, d.read(3));
+ assertEquals(10, d.seek(10));
+ assertEquals(0, d.read(3));
d.stop();
d.close();
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFilterTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFilterTest.java
index bde450f..3f4b683 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFilterTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFilterTest.java
@@ -85,6 +85,7 @@
AvSettings
.builder(Filter.TYPE_TS, true) // is Audio
.setPassthrough(false)
+ .setUseSecureMemory(false)
.setAudioStreamType(AvSettings.AUDIO_STREAM_TYPE_MPEG1)
.build();
@@ -94,10 +95,14 @@
} else {
assertEquals(settings.getAudioStreamType(), AvSettings.AUDIO_STREAM_TYPE_UNDEFINED);
}
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+ assertEquals(settings.useSecureMemory(), false);
+ }
settings = AvSettings
.builder(Filter.TYPE_TS, false) // is Video
.setPassthrough(false)
+ .setUseSecureMemory(true)
.setVideoStreamType(AvSettings.VIDEO_STREAM_TYPE_MPEG1)
.build();
@@ -107,6 +112,9 @@
} else {
assertEquals(settings.getVideoStreamType(), AvSettings.VIDEO_STREAM_TYPE_UNDEFINED);
}
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+ assertEquals(settings.useSecureMemory(), true);
+ }
}
@Test
@@ -193,6 +201,48 @@
}
@Test
+ public void testMmtpSectionSettingsWithSectionBits() throws Exception {
+ SectionSettingsWithSectionBits settings =
+ SectionSettingsWithSectionBits.builder(Filter.TYPE_MMTP)
+ .setCrcEnabled(true)
+ .setBitWidthOfLengthField(16)
+ .setRepeat(false)
+ .setRaw(false)
+ .setFilter(new byte[] {2, 3, 4})
+ .setMask(new byte[] {7, 6, 5, 4})
+ .setMode(new byte[] {22, 55, 33})
+ .build();
+
+ assertTrue(settings.isCrcEnabled());
+ assertFalse(settings.isRepeat());
+ assertFalse(settings.isRaw());
+ assertEquals(settings.getBitWidthOfLengthField(), 16);
+ Assert.assertArrayEquals(new byte[] {2, 3, 4}, settings.getFilterBytes());
+ Assert.assertArrayEquals(new byte[] {7, 6, 5, 4}, settings.getMask());
+ Assert.assertArrayEquals(new byte[] {22, 55, 33}, settings.getMode());
+ }
+
+ @Test
+ public void testMMtpSectionSettingsWithTableInfo() throws Exception {
+ SectionSettingsWithTableInfo settings =
+ SectionSettingsWithTableInfo.builder(Filter.TYPE_MMTP)
+ .setTableId(11)
+ .setVersion(2)
+ .setCrcEnabled(true)
+ .setBitWidthOfLengthField(32)
+ .setRepeat(true)
+ .setRaw(true)
+ .build();
+
+ assertEquals(11, settings.getTableId());
+ assertEquals(2, settings.getVersion());
+ assertTrue(settings.isCrcEnabled());
+ assertEquals(settings.getBitWidthOfLengthField(), 32);
+ assertTrue(settings.isRepeat());
+ assertTrue(settings.isRaw());
+ }
+
+ @Test
public void testAlpFilterConfiguration() throws Exception {
AlpFilterConfiguration config =
AlpFilterConfiguration
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java
index 9bd036a..2a923fe 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java
@@ -99,7 +99,7 @@
}
@Test
- public void testAnalogFrontendSettings() throws Exception {
+ public void testAnalogFrontendSettingsWithIntFrequency() throws Exception {
AnalogFrontendSettings settings =
AnalogFrontendSettings
.builder()
@@ -136,7 +136,7 @@
}
@Test
- public void testAtsc3FrontendSettings() throws Exception {
+ public void testAtsc3FrontendSettingsWithIntFrequency() throws Exception {
Atsc3PlpSettings plp1 =
Atsc3PlpSettings
.builder()
@@ -201,7 +201,7 @@
}
@Test
- public void testAtscFrontendSettings() throws Exception {
+ public void testAtscFrontendSettingsWithIntFrequency() throws Exception {
AtscFrontendSettings settings =
AtscFrontendSettings
.builder()
@@ -227,7 +227,7 @@
}
@Test
- public void testDvbcFrontendSettings() throws Exception {
+ public void testDvbcFrontendSettingsWithIntFrequency() throws Exception {
DvbcFrontendSettings settings =
DvbcFrontendSettings
.builder()
@@ -267,7 +267,7 @@
}
@Test
- public void testDvbsFrontendSettings() throws Exception {
+ public void testDvbsFrontendSettingsWithIntFrequency() throws Exception {
DvbsCodeRate codeRate =
DvbsCodeRate
.builder()
@@ -328,7 +328,7 @@
}
@Test
- public void testDvbtFrontendSettings() throws Exception {
+ public void testDvbtFrontendSettingsWithIntFrequency() throws Exception {
DvbtFrontendSettings settings =
DvbtFrontendSettings
.builder()
@@ -382,7 +382,7 @@
}
@Test
- public void testIsdbs3FrontendSettings() throws Exception {
+ public void testIsdbs3FrontendSettingsWithIntFrequency() throws Exception {
Isdbs3FrontendSettings settings =
Isdbs3FrontendSettings
.builder()
@@ -418,7 +418,7 @@
}
@Test
- public void testIsdbsFrontendSettings() throws Exception {
+ public void testIsdbsFrontendSettingsWithIntFrequency() throws Exception {
IsdbsFrontendSettings settings =
IsdbsFrontendSettings
.builder()
@@ -455,28 +455,46 @@
}
@Test
- public void testIsdbtFrontendSettings() throws Exception {
- IsdbtFrontendSettings settings =
- IsdbtFrontendSettings
- .builder()
- .setFrequency(9)
- .setModulation(IsdbtFrontendSettings.MODULATION_MOD_64QAM)
- .setBandwidth(IsdbtFrontendSettings.BANDWIDTH_8MHZ)
- .setMode(IsdbtFrontendSettings.MODE_2)
- .setCodeRate(DvbtFrontendSettings.CODERATE_7_8)
- .setGuardInterval(DvbtFrontendSettings.GUARD_INTERVAL_1_4)
- .setServiceAreaId(10)
- .build();
+ public void testIsdbtFrontendSettingsWithIntFrequency() throws Exception {
+ IsdbtFrontendSettings.Builder builder = IsdbtFrontendSettings.builder();
+ builder.setFrequency(9);
+ builder.setModulation(IsdbtFrontendSettings.MODULATION_MOD_64QAM);
+ builder.setBandwidth(IsdbtFrontendSettings.BANDWIDTH_8MHZ);
+ builder.setMode(IsdbtFrontendSettings.MODE_2);
+ builder.setCodeRate(DvbtFrontendSettings.CODERATE_7_8);
+ builder.setGuardInterval(DvbtFrontendSettings.GUARD_INTERVAL_1_4);
+ builder.setServiceAreaId(10);
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+ IsdbtFrontendSettings.IsdbtLayerSettings.Builder layerBuilder =
+ IsdbtFrontendSettings.IsdbtLayerSettings.builder();
+ layerBuilder.setTimeInterleaveMode(IsdbtFrontendSettings.TIME_INTERLEAVE_MODE_AUTO);
+ layerBuilder.setModulation(IsdbtFrontendSettings.MODULATION_MOD_16QAM);
+ layerBuilder.setCodeRate(DvbtFrontendSettings.CODERATE_5_6);
+ layerBuilder.setNumberOfSegments(0xFF);
+ IsdbtFrontendSettings.IsdbtLayerSettings layer = layerBuilder.build();
+ builder.setLayerSettings(new IsdbtFrontendSettings.IsdbtLayerSettings[] {layer, layer});
+ builder.setPartialReceptionFlag(IsdbtFrontendSettings.PARTIAL_RECEPTION_FLAG_TRUE);
+ }
+
+ IsdbtFrontendSettings settings = builder.build();
settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
settings.setEndFrequency(100);
assertEquals(FrontendSettings.TYPE_ISDBT, settings.getType());
assertEquals(9, settings.getFrequency());
- assertEquals(IsdbtFrontendSettings.MODULATION_MOD_64QAM, settings.getModulation());
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+ assertEquals(IsdbtFrontendSettings.MODULATION_UNDEFINED, settings.getModulation());
+ } else {
+ assertEquals(IsdbtFrontendSettings.MODULATION_MOD_64QAM, settings.getModulation());
+ }
assertEquals(IsdbtFrontendSettings.BANDWIDTH_8MHZ, settings.getBandwidth());
assertEquals(IsdbtFrontendSettings.MODE_2, settings.getMode());
- assertEquals(DvbtFrontendSettings.CODERATE_7_8, settings.getCodeRate());
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+ assertEquals(DvbtFrontendSettings.CODERATE_UNDEFINED, settings.getCodeRate());
+ } else {
+ assertEquals(DvbtFrontendSettings.CODERATE_7_8, settings.getCodeRate());
+ }
assertEquals(DvbtFrontendSettings.GUARD_INTERVAL_1_4, settings.getGuardInterval());
assertEquals(10, settings.getServiceAreaId());
if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
@@ -488,10 +506,27 @@
settings.getFrontendSpectralInversion());
assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequency());
}
+
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+ IsdbtFrontendSettings.IsdbtLayerSettings[] layers = settings.getLayerSettings();
+ assertEquals(layers.length, 2);
+ assertEquals(layers[0].getModulation(), IsdbtFrontendSettings.MODULATION_MOD_16QAM);
+ assertEquals(layers[0].getCodeRate(), DvbtFrontendSettings.CODERATE_5_6);
+ assertEquals(layers[0].getTimeInterleaveMode(),
+ IsdbtFrontendSettings.TIME_INTERLEAVE_MODE_AUTO);
+ assertEquals(layers[0].getNumberOfSegments(), 0xFF);
+ assertEquals(layers[1].getModulation(), IsdbtFrontendSettings.MODULATION_MOD_16QAM);
+ assertEquals(layers[1].getCodeRate(), DvbtFrontendSettings.CODERATE_5_6);
+ assertEquals(layers[1].getTimeInterleaveMode(),
+ IsdbtFrontendSettings.TIME_INTERLEAVE_MODE_AUTO);
+ assertEquals(layers[1].getNumberOfSegments(), 0xFF);
+ assertEquals(settings.getPartialReceptionFlag(),
+ IsdbtFrontendSettings.PARTIAL_RECEPTION_FLAG_TRUE);
+ }
}
@Test
- public void testDtmbFrontendSettings() throws Exception {
+ public void testDtmbFrontendSettingsWithIntFrequency() throws Exception {
if (!TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
TAG + ": testDtmbFrontendSettings")) {
return;
@@ -521,8 +556,9 @@
}
@Test
- public void testFrontendInfo() throws Exception {
+ public void testFrontendInfoWithIntFrequency() throws Exception {
List<Integer> ids = mTuner.getFrontendIds();
+ if (ids == null) return;
List<FrontendInfo> infos = mTuner.getAvailableFrontendInfos();
Map<Integer, FrontendInfo> infoMap = new HashMap<>();
for (FrontendInfo info : infos) {
@@ -583,6 +619,526 @@
assertTrue(infoMap.isEmpty());
}
+ @Test
+ public void testAnalogFrontendSettingsWithLongFrequency() throws Exception {
+ AnalogFrontendSettings settings =
+ AnalogFrontendSettings
+ .builder()
+ .setFrequencyLong(1)
+ .setSignalType(AnalogFrontendSettings.SIGNAL_TYPE_NTSC)
+ .setSifStandard(AnalogFrontendSettings.SIF_BG_NICAM)
+ .setAftFlag(AnalogFrontendSettings.AFT_FLAG_TRUE)
+ .build();
+
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+ settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+ settings.setEndFrequencyLong(100);
+ } else {
+ settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+ settings.setEndFrequencyLong(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY);
+ }
+
+ assertEquals(FrontendSettings.TYPE_ANALOG, settings.getType());
+ assertEquals(1, settings.getFrequencyLong());
+ assertEquals(AnalogFrontendSettings.SIGNAL_TYPE_NTSC, settings.getSignalType());
+ assertEquals(AnalogFrontendSettings.SIF_BG_NICAM, settings.getSifStandard());
+ assertEquals(AnalogFrontendSettings.AFT_FLAG_TRUE, settings.getAftFlag());
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+ assertEquals(AnalogFrontendSettings.AFT_FLAG_TRUE, settings.getAftFlag());
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+ settings.getFrontendSpectralInversion());
+ assertEquals(100, settings.getEndFrequencyLong());
+ } else {
+ assertEquals(AnalogFrontendSettings.AFT_FLAG_UNDEFINED, settings.getAftFlag());
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+ settings.getFrontendSpectralInversion());
+ assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+ }
+ }
+
+ @Test
+ public void testAtsc3FrontendSettingsWithLongFrequency() throws Exception {
+ Atsc3PlpSettings plp1 =
+ Atsc3PlpSettings
+ .builder()
+ .setPlpId(1)
+ .setModulation(Atsc3FrontendSettings.MODULATION_MOD_QPSK)
+ .setInterleaveMode(Atsc3FrontendSettings.TIME_INTERLEAVE_MODE_AUTO)
+ .setCodeRate(Atsc3FrontendSettings.CODERATE_6_15)
+ .setFec(Atsc3FrontendSettings.FEC_BCH_LDPC_64K)
+ .build();
+
+ Atsc3PlpSettings plp2 =
+ Atsc3PlpSettings
+ .builder()
+ .setPlpId(2)
+ .setModulation(Atsc3FrontendSettings.MODULATION_MOD_QPSK)
+ .setInterleaveMode(Atsc3FrontendSettings.TIME_INTERLEAVE_MODE_HTI)
+ .setCodeRate(Atsc3FrontendSettings.CODERATE_UNDEFINED)
+ .setFec(Atsc3FrontendSettings.FEC_LDPC_16K)
+ .build();
+
+ Atsc3FrontendSettings settings =
+ Atsc3FrontendSettings
+ .builder()
+ .setFrequencyLong(2)
+ .setBandwidth(Atsc3FrontendSettings.BANDWIDTH_BANDWIDTH_6MHZ)
+ .setDemodOutputFormat(Atsc3FrontendSettings.MODULATION_MOD_QPSK)
+ .setPlpSettings(new Atsc3PlpSettings[] {plp1, plp2})
+ .build();
+
+ settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+ settings.setEndFrequencyLong(100);
+
+ assertEquals(FrontendSettings.TYPE_ATSC3, settings.getType());
+ assertEquals(2, settings.getFrequencyLong());
+ assertEquals(Atsc3FrontendSettings.BANDWIDTH_BANDWIDTH_6MHZ, settings.getBandwidth());
+ assertEquals(Atsc3FrontendSettings.MODULATION_MOD_QPSK, settings.getDemodOutputFormat());
+
+ Atsc3PlpSettings[] plps = settings.getPlpSettings();
+ assertEquals(2, plps.length);
+
+ assertEquals(1, plps[0].getPlpId());
+ assertEquals(Atsc3FrontendSettings.MODULATION_MOD_QPSK, plps[0].getModulation());
+ assertEquals(Atsc3FrontendSettings.TIME_INTERLEAVE_MODE_AUTO, plps[0].getInterleaveMode());
+ assertEquals(Atsc3FrontendSettings.CODERATE_6_15, plps[0].getCodeRate());
+ assertEquals(Atsc3FrontendSettings.FEC_BCH_LDPC_64K, plps[0].getFec());
+
+ assertEquals(2, plps[1].getPlpId());
+ assertEquals(Atsc3FrontendSettings.MODULATION_MOD_QPSK, plps[1].getModulation());
+ assertEquals(Atsc3FrontendSettings.TIME_INTERLEAVE_MODE_HTI, plps[1].getInterleaveMode());
+ assertEquals(Atsc3FrontendSettings.CODERATE_UNDEFINED, plps[1].getCodeRate());
+ assertEquals(Atsc3FrontendSettings.FEC_LDPC_16K, plps[1].getFec());
+
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+ settings.getFrontendSpectralInversion());
+ assertEquals(100, settings.getEndFrequencyLong());
+ } else {
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+ settings.getFrontendSpectralInversion());
+ assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+ }
+ }
+
+ @Test
+ public void testAtscFrontendSettingsWithLongFrequency() throws Exception {
+ AtscFrontendSettings settings =
+ AtscFrontendSettings
+ .builder()
+ .setFrequencyLong(3)
+ .setModulation(AtscFrontendSettings.MODULATION_MOD_8VSB)
+ .build();
+
+ settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+ settings.setEndFrequencyLong(100);
+
+ assertEquals(FrontendSettings.TYPE_ATSC, settings.getType());
+ assertEquals(3, settings.getFrequencyLong());
+ assertEquals(AtscFrontendSettings.MODULATION_MOD_8VSB, settings.getModulation());
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+ settings.getFrontendSpectralInversion());
+ assertEquals(100, settings.getEndFrequencyLong());
+ } else {
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+ settings.getFrontendSpectralInversion());
+ assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+ }
+ }
+
+ @Test
+ public void testDvbcFrontendSettingsWithLongFrequency() throws Exception {
+ DvbcFrontendSettings settings =
+ DvbcFrontendSettings
+ .builder()
+ .setFrequencyLong(4)
+ .setModulation(DvbcFrontendSettings.MODULATION_MOD_32QAM)
+ .setInnerFec(FrontendSettings.FEC_8_15)
+ .setSymbolRate(3)
+ .setOuterFec(DvbcFrontendSettings.OUTER_FEC_OUTER_FEC_RS)
+ .setAnnex(DvbcFrontendSettings.ANNEX_B)
+ .setTimeInterleaveMode(DvbcFrontendSettings.TIME_INTERLEAVE_MODE_AUTO)
+ .setBandwidth(DvbcFrontendSettings.BANDWIDTH_5MHZ)
+ // DvbcFrontendSettings.SpectralInversion is deprecated in Android 12. Use
+ // FrontendSettings.FrontendSpectralInversion instead.
+ .setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL)
+ .build();
+
+ settings.setEndFrequencyLong(100);
+
+ assertEquals(FrontendSettings.TYPE_DVBC, settings.getType());
+ assertEquals(4, settings.getFrequencyLong());
+ assertEquals(DvbcFrontendSettings.MODULATION_MOD_32QAM, settings.getModulation());
+ assertEquals(FrontendSettings.FEC_8_15, settings.getInnerFec());
+ assertEquals(3, settings.getSymbolRate());
+ assertEquals(DvbcFrontendSettings.OUTER_FEC_OUTER_FEC_RS, settings.getOuterFec());
+ assertEquals(DvbcFrontendSettings.ANNEX_B, settings.getAnnex());
+ assertEquals(DvbcFrontendSettings.TIME_INTERLEAVE_MODE_AUTO,
+ settings.getTimeInterleaveMode());
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+ settings.getSpectralInversion());
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+ assertEquals(100, settings.getEndFrequencyLong());
+ assertEquals(DvbcFrontendSettings.BANDWIDTH_5MHZ, settings.getBandwidth());
+ } else {
+ assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+ assertEquals(DvbcFrontendSettings.BANDWIDTH_UNDEFINED, settings.getBandwidth());
+ }
+ }
+
+ @Test
+ public void testDvbsFrontendSettingsWithLongFrequency() throws Exception {
+ DvbsCodeRate codeRate =
+ DvbsCodeRate
+ .builder()
+ .setInnerFec(FrontendSettings.FEC_9_10)
+ .setLinear(true)
+ .setShortFrameEnabled(false)
+ .setBitsPer1000Symbol(55)
+ .build();
+
+ DvbsFrontendSettings settings =
+ DvbsFrontendSettings
+ .builder()
+ .setFrequencyLong(5)
+ .setModulation(DvbsFrontendSettings.MODULATION_MOD_ACM)
+ .setCodeRate(codeRate)
+ .setSymbolRate(3)
+ .setRolloff(DvbsFrontendSettings.ROLLOFF_0_15)
+ .setPilot(DvbsFrontendSettings.PILOT_OFF)
+ .setInputStreamId(1)
+ .setStandard(DvbsFrontendSettings.STANDARD_S2)
+ .setVcmMode(DvbsFrontendSettings.VCM_MODE_MANUAL)
+ .setScanType(DvbsFrontendSettings.SCAN_TYPE_DIRECT)
+ .setCanHandleDiseqcRxMessage(true)
+ .build();
+
+ settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+ settings.setEndFrequencyLong(100);
+
+ assertEquals(FrontendSettings.TYPE_DVBS, settings.getType());
+ assertEquals(5, settings.getFrequencyLong());
+ assertEquals(DvbsFrontendSettings.MODULATION_MOD_ACM, settings.getModulation());
+ assertEquals(3, settings.getSymbolRate());
+ assertEquals(DvbsFrontendSettings.ROLLOFF_0_15, settings.getRolloff());
+ assertEquals(DvbsFrontendSettings.PILOT_OFF, settings.getPilot());
+ assertEquals(1, settings.getInputStreamId());
+ assertEquals(DvbsFrontendSettings.STANDARD_S2, settings.getStandard());
+ assertEquals(DvbsFrontendSettings.VCM_MODE_MANUAL, settings.getVcmMode());
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+ assertEquals(DvbsFrontendSettings.SCAN_TYPE_DIRECT, settings.getScanType());
+ assertTrue(settings.canHandleDiseqcRxMessage());
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+ settings.getFrontendSpectralInversion());
+ assertEquals(100, settings.getEndFrequencyLong());
+ } else {
+ assertEquals(DvbsFrontendSettings.SCAN_TYPE_UNDEFINED, settings.getScanType());
+ assertFalse(settings.canHandleDiseqcRxMessage());
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+ settings.getFrontendSpectralInversion());
+ assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+ }
+
+ DvbsCodeRate cr = settings.getCodeRate();
+ assertNotNull(cr);
+ assertEquals(FrontendSettings.FEC_9_10, cr.getInnerFec());
+ assertEquals(true, cr.isLinear());
+ assertEquals(false, cr.isShortFrameEnabled());
+ assertEquals(55, cr.getBitsPer1000Symbol());
+ }
+
+ @Test
+ public void testDvbtFrontendSettingsWithLongFrequency() throws Exception {
+ DvbtFrontendSettings settings =
+ DvbtFrontendSettings
+ .builder()
+ .setFrequencyLong(6)
+ .setTransmissionMode(DvbtFrontendSettings.TRANSMISSION_MODE_EXTENDED_32K)
+ .setBandwidth(DvbtFrontendSettings.BANDWIDTH_1_7MHZ)
+ .setConstellation(DvbtFrontendSettings.CONSTELLATION_16QAM_R)
+ .setHierarchy(DvbtFrontendSettings.HIERARCHY_4_NATIVE)
+ .setHighPriorityCodeRate(DvbtFrontendSettings.CODERATE_6_7)
+ .setLowPriorityCodeRate(DvbtFrontendSettings.CODERATE_2_3)
+ .setGuardInterval(DvbtFrontendSettings.GUARD_INTERVAL_19_128)
+ .setHighPriority(true)
+ .setStandard(DvbtFrontendSettings.STANDARD_T2)
+ .setMiso(false)
+ .setPlpMode(DvbtFrontendSettings.PLP_MODE_MANUAL)
+ .setPlpId(333)
+ .setPlpGroupId(777)
+ .build();
+
+ settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+ settings.setEndFrequencyLong(100);
+
+ assertEquals(FrontendSettings.TYPE_DVBT, settings.getType());
+ assertEquals(6, settings.getFrequencyLong());
+ assertEquals(DvbtFrontendSettings.BANDWIDTH_1_7MHZ, settings.getBandwidth());
+ assertEquals(DvbtFrontendSettings.HIERARCHY_4_NATIVE, settings.getHierarchy());
+ assertEquals(DvbtFrontendSettings.CODERATE_6_7, settings.getHighPriorityCodeRate());
+ assertEquals(DvbtFrontendSettings.CODERATE_2_3, settings.getLowPriorityCodeRate());
+ assertEquals(DvbtFrontendSettings.GUARD_INTERVAL_19_128, settings.getGuardInterval());
+ assertEquals(true, settings.isHighPriority());
+ assertEquals(DvbtFrontendSettings.STANDARD_T2, settings.getStandard());
+ assertEquals(false, settings.isMiso());
+ assertEquals(DvbtFrontendSettings.PLP_MODE_MANUAL, settings.getPlpMode());
+ assertEquals(333, settings.getPlpId());
+ assertEquals(777, settings.getPlpGroupId());
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+ assertEquals(DvbtFrontendSettings.TRANSMISSION_MODE_EXTENDED_32K,
+ settings.getTransmissionMode());
+ assertEquals(DvbtFrontendSettings.CONSTELLATION_16QAM_R, settings.getConstellation());
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+ settings.getFrontendSpectralInversion());
+ assertEquals(100, settings.getEndFrequencyLong());
+ } else {
+ assertEquals(DvbtFrontendSettings.TRANSMISSION_MODE_UNDEFINED,
+ settings.getTransmissionMode());
+ assertEquals(DvbtFrontendSettings.CONSTELLATION_UNDEFINED, settings.getConstellation());
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+ settings.getFrontendSpectralInversion());
+ assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+ }
+ }
+
+ @Test
+ public void testIsdbs3FrontendSettingsWithLongFrequency() throws Exception {
+ Isdbs3FrontendSettings settings =
+ Isdbs3FrontendSettings
+ .builder()
+ .setFrequencyLong(7)
+ .setStreamId(2)
+ .setStreamIdType(IsdbsFrontendSettings.STREAM_ID_TYPE_ID)
+ .setModulation(Isdbs3FrontendSettings.MODULATION_MOD_BPSK)
+ .setCodeRate(Isdbs3FrontendSettings.CODERATE_1_3)
+ .setSymbolRate(555)
+ .setRolloff(Isdbs3FrontendSettings.ROLLOFF_0_03)
+ .build();
+
+ settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+ settings.setEndFrequencyLong(100);
+
+ assertEquals(FrontendSettings.TYPE_ISDBS3, settings.getType());
+ assertEquals(7, settings.getFrequencyLong());
+ assertEquals(2, settings.getStreamId());
+ assertEquals(IsdbsFrontendSettings.STREAM_ID_TYPE_ID, settings.getStreamIdType());
+ assertEquals(Isdbs3FrontendSettings.MODULATION_MOD_BPSK, settings.getModulation());
+ assertEquals(Isdbs3FrontendSettings.CODERATE_1_3, settings.getCodeRate());
+ assertEquals(555, settings.getSymbolRate());
+ assertEquals(Isdbs3FrontendSettings.ROLLOFF_0_03, settings.getRolloff());
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+ settings.getFrontendSpectralInversion());
+ assertEquals(100, settings.getEndFrequencyLong());
+ } else {
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+ settings.getFrontendSpectralInversion());
+ assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+ }
+ }
+
+ @Test
+ public void testIsdbsFrontendSettingsWithLongFrequency() throws Exception {
+ IsdbsFrontendSettings settings =
+ IsdbsFrontendSettings
+ .builder()
+ .setFrequencyLong(8)
+ .setStreamId(3)
+ .setStreamIdType(IsdbsFrontendSettings.STREAM_ID_TYPE_RELATIVE_NUMBER)
+ .setModulation(IsdbsFrontendSettings.MODULATION_AUTO)
+ .setCodeRate(IsdbsFrontendSettings.CODERATE_3_4)
+ .setSymbolRate(667)
+ .setRolloff(IsdbsFrontendSettings.ROLLOFF_0_35)
+ .build();
+
+ settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+ settings.setEndFrequencyLong(100);
+
+ assertEquals(FrontendSettings.TYPE_ISDBS, settings.getType());
+ assertEquals(8, settings.getFrequencyLong());
+ assertEquals(3, settings.getStreamId());
+ assertEquals(
+ IsdbsFrontendSettings.STREAM_ID_TYPE_RELATIVE_NUMBER, settings.getStreamIdType());
+ assertEquals(IsdbsFrontendSettings.MODULATION_AUTO, settings.getModulation());
+ assertEquals(IsdbsFrontendSettings.CODERATE_3_4, settings.getCodeRate());
+ assertEquals(667, settings.getSymbolRate());
+ assertEquals(IsdbsFrontendSettings.ROLLOFF_0_35, settings.getRolloff());
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+ settings.getFrontendSpectralInversion());
+ assertEquals(100, settings.getEndFrequencyLong());
+ } else {
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+ settings.getFrontendSpectralInversion());
+ assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+ }
+ }
+
+ @Test
+ public void testIsdbtFrontendSettingsWithLongFrequency() throws Exception {
+ IsdbtFrontendSettings.Builder builder = IsdbtFrontendSettings.builder();
+ builder.setFrequencyLong(9);
+ builder.setModulation(IsdbtFrontendSettings.MODULATION_MOD_64QAM);
+ builder.setBandwidth(IsdbtFrontendSettings.BANDWIDTH_8MHZ);
+ builder.setMode(IsdbtFrontendSettings.MODE_2);
+ builder.setCodeRate(DvbtFrontendSettings.CODERATE_7_8);
+ builder.setGuardInterval(DvbtFrontendSettings.GUARD_INTERVAL_1_4);
+ builder.setServiceAreaId(10);
+
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+ IsdbtFrontendSettings.IsdbtLayerSettings.Builder layerBuilder =
+ IsdbtFrontendSettings.IsdbtLayerSettings.builder();
+ layerBuilder.setTimeInterleaveMode(IsdbtFrontendSettings.TIME_INTERLEAVE_MODE_AUTO);
+ layerBuilder.setModulation(IsdbtFrontendSettings.MODULATION_MOD_16QAM);
+ layerBuilder.setCodeRate(DvbtFrontendSettings.CODERATE_5_6);
+ layerBuilder.setNumberOfSegments(0xFF);
+ IsdbtFrontendSettings.IsdbtLayerSettings layer = layerBuilder.build();
+ builder.setLayerSettings(new IsdbtFrontendSettings.IsdbtLayerSettings[] {layer, layer});
+ builder.setPartialReceptionFlag(IsdbtFrontendSettings.PARTIAL_RECEPTION_FLAG_TRUE);
+ }
+
+ IsdbtFrontendSettings settings = builder.build();
+ settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+ settings.setEndFrequencyLong(100);
+
+ assertEquals(FrontendSettings.TYPE_ISDBT, settings.getType());
+ assertEquals(9, settings.getFrequencyLong());
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+ assertEquals(IsdbtFrontendSettings.MODULATION_UNDEFINED, settings.getModulation());
+ } else {
+ assertEquals(IsdbtFrontendSettings.MODULATION_MOD_64QAM, settings.getModulation());
+ }
+ assertEquals(IsdbtFrontendSettings.BANDWIDTH_8MHZ, settings.getBandwidth());
+ assertEquals(IsdbtFrontendSettings.MODE_2, settings.getMode());
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+ assertEquals(DvbtFrontendSettings.CODERATE_UNDEFINED, settings.getCodeRate());
+ } else {
+ assertEquals(DvbtFrontendSettings.CODERATE_7_8, settings.getCodeRate());
+ }
+ assertEquals(DvbtFrontendSettings.GUARD_INTERVAL_1_4, settings.getGuardInterval());
+ assertEquals(10, settings.getServiceAreaId());
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+ settings.getFrontendSpectralInversion());
+ assertEquals(100, settings.getEndFrequencyLong());
+ } else {
+ assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+ settings.getFrontendSpectralInversion());
+ assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+ }
+
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+ IsdbtFrontendSettings.IsdbtLayerSettings[] layers = settings.getLayerSettings();
+ assertEquals(layers.length, 2);
+ assertEquals(layers[0].getModulation(), IsdbtFrontendSettings.MODULATION_MOD_16QAM);
+ assertEquals(layers[0].getCodeRate(), DvbtFrontendSettings.CODERATE_5_6);
+ assertEquals(layers[0].getTimeInterleaveMode(),
+ IsdbtFrontendSettings.TIME_INTERLEAVE_MODE_AUTO);
+ assertEquals(layers[0].getNumberOfSegments(), 0xFF);
+ assertEquals(layers[1].getModulation(), IsdbtFrontendSettings.MODULATION_MOD_16QAM);
+ assertEquals(layers[1].getCodeRate(), DvbtFrontendSettings.CODERATE_5_6);
+ assertEquals(layers[1].getTimeInterleaveMode(),
+ IsdbtFrontendSettings.TIME_INTERLEAVE_MODE_AUTO);
+ assertEquals(layers[1].getNumberOfSegments(), 0xFF);
+ assertEquals(settings.getPartialReceptionFlag(),
+ IsdbtFrontendSettings.PARTIAL_RECEPTION_FLAG_TRUE);
+ }
+ }
+
+ @Test
+ public void testDtmbFrontendSettingsWithLongFrequency() throws Exception {
+ if (!TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
+ TAG + ": testDtmbFrontendSettings")) {
+ return;
+ }
+ DtmbFrontendSettings settings =
+ DtmbFrontendSettings
+ .builder()
+ .setFrequencyLong(6)
+ .setModulation(DtmbFrontendSettings.MODULATION_CONSTELLATION_4QAM)
+ .setCodeRate(DtmbFrontendSettings.CODERATE_2_5)
+ .setTransmissionMode(DtmbFrontendSettings.TRANSMISSION_MODE_C1)
+ .setBandwidth(DtmbFrontendSettings.BANDWIDTH_8MHZ)
+ .setTimeInterleaveMode(
+ DtmbFrontendSettings.TIME_INTERLEAVE_MODE_TIMER_INT_240)
+ .setGuardInterval(DtmbFrontendSettings.GUARD_INTERVAL_PN_945_VARIOUS)
+ .build();
+ assertEquals(FrontendSettings.TYPE_DTMB, settings.getType());
+ assertEquals(6, settings.getFrequencyLong());
+ assertEquals(DtmbFrontendSettings.TRANSMISSION_MODE_C1, settings.getTransmissionMode());
+ assertEquals(DtmbFrontendSettings.BANDWIDTH_8MHZ, settings.getBandwidth());
+ assertEquals(DtmbFrontendSettings.MODULATION_CONSTELLATION_4QAM, settings.getModulation());
+ assertEquals(DtmbFrontendSettings.TIME_INTERLEAVE_MODE_TIMER_INT_240,
+ settings.getTimeInterleaveMode());
+ assertEquals(DtmbFrontendSettings.CODERATE_2_5, settings.getCodeRate());
+ assertEquals(DtmbFrontendSettings.GUARD_INTERVAL_PN_945_VARIOUS,
+ settings.getGuardInterval());
+ }
+
+ @Test
+ public void testFrontendInfoWithLongFrequency() throws Exception {
+ List<Integer> ids = mTuner.getFrontendIds();
+ List<FrontendInfo> infos = mTuner.getAvailableFrontendInfos();
+ Map<Integer, FrontendInfo> infoMap = new HashMap<>();
+ for (FrontendInfo info : infos) {
+ infoMap.put(info.getId(), info);
+ }
+ for (int id : ids) {
+ FrontendInfo info = mTuner.getFrontendInfoById(id);
+ FrontendInfo infoFromMap = infoMap.get(id);
+ assertNotNull(info);
+ assertThat(info).isEqualTo(infoFromMap);
+ assertEquals(id, info.getId());
+ assertTrue(info.getFrequencyRangeLong().getLower() > 0);
+ assertTrue(info.getSymbolRateRange().getLower() >= 0);
+ assertTrue(info.getAcquireRangeLong() > 0);
+ info.getExclusiveGroupId();
+ info.getStatusCapabilities();
+
+ FrontendCapabilities caps = info.getFrontendCapabilities();
+ if (info.getType() <= FrontendSettings.TYPE_ISDBT) {
+ assertNotNull(caps);
+ }
+ switch(info.getType()) {
+ case FrontendSettings.TYPE_ANALOG:
+ testAnalogFrontendCapabilities(caps);
+ break;
+ case FrontendSettings.TYPE_ATSC3:
+ testAtsc3FrontendCapabilities(caps);
+ break;
+ case FrontendSettings.TYPE_ATSC:
+ testAtscFrontendCapabilities(caps);
+ break;
+ case FrontendSettings.TYPE_DVBC:
+ testDvbcFrontendCapabilities(caps);
+ break;
+ case FrontendSettings.TYPE_DVBS:
+ testDvbsFrontendCapabilities(caps);
+ break;
+ case FrontendSettings.TYPE_DVBT:
+ testDvbtFrontendCapabilities(caps);
+ break;
+ case FrontendSettings.TYPE_ISDBS3:
+ testIsdbs3FrontendCapabilities(caps);
+ break;
+ case FrontendSettings.TYPE_ISDBS:
+ testIsdbsFrontendCapabilities(caps);
+ break;
+ case FrontendSettings.TYPE_ISDBT:
+ testIsdbtFrontendCapabilities(caps);
+ break;
+ case FrontendSettings.TYPE_DTMB:
+ testDtmbFrontendCapabilities(caps);
+ break;
+ default:
+ break;
+ }
+ infoMap.remove(id);
+ }
+ assertTrue(infoMap.isEmpty());
+ }
+
private void testAnalogFrontendCapabilities(FrontendCapabilities caps) throws Exception {
assertTrue(caps instanceof AnalogFrontendCapabilities);
AnalogFrontendCapabilities analogCaps = (AnalogFrontendCapabilities) caps;
@@ -660,6 +1216,9 @@
isdbtCaps.getModulationCapability();
isdbtCaps.getCodeRateCapability();
isdbtCaps.getGuardIntervalCapability();
+ isdbtCaps.getTimeInterleaveModeCapability();
+ isdbtCaps.isSegmentAutoSupported();
+ isdbtCaps.isFullSegmentSupported();
}
private void testDtmbFrontendCapabilities(FrontendCapabilities caps) throws Exception {
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerResourceTestService.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerResourceTestService.java
new file mode 100644
index 0000000..e714508
--- /dev/null
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerResourceTestService.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.cts;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.tv.tuner.Result;
+import android.media.tv.tuner.Tuner;
+import android.media.tv.tuner.frontend.FrontendInfo;
+import android.media.tv.tuner.frontend.FrontendSettings;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.List;
+
+public class TunerResourceTestService extends Service {
+ private static final String TAG = "TunerResourceTestService";
+ private Context mContext = null;
+ private Tuner mTuner = null;
+ private FrontendSettings mFeSettings;
+ private FrontendInfo mFeInfo;
+ private final Object mLock = new Object();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ mContext = this;
+ return mBinder;
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ synchronized (mLock) {
+ closeTunerInternal();
+ return false;
+ }
+ }
+
+ private void closeTunerInternal() {
+ if (mTuner != null) {
+ mTuner.close();
+ mTuner = null;
+ }
+ }
+
+ private int tuneInternal(int frontendIndex) {
+ // make sure mTuner is not null
+ if (mTuner == null) {
+ Log.e(TAG, "tune called on null tuner");
+ return Result.INVALID_STATE;
+ }
+
+ // construct FrontendSettings to tune
+ List<FrontendInfo> infos = mTuner.getAvailableFrontendInfos();
+ mFeInfo = infos.get(frontendIndex);
+ mFeSettings = TunerTest.createFrontendSettings(mFeInfo);
+
+ // tune
+ return mTuner.tune(mFeSettings);
+ }
+
+ private final ITunerResourceTestServer.Stub mBinder = new ITunerResourceTestServer.Stub() {
+ public void createTuner(int useCase) {
+ synchronized (mLock) {
+ closeTunerInternal();
+ mTuner = new Tuner(mContext, null, useCase);
+ }
+ }
+
+ public void tuneAsync(int frontendIndex) {
+ synchronized (mLock) {
+ tuneInternal(frontendIndex);
+ }
+ }
+
+ public int tune(int frontendIndex) {
+ synchronized (mLock) {
+ return tuneInternal(frontendIndex);
+ }
+ }
+
+ public void closeTuner() {
+ synchronized (mLock) {
+ closeTunerInternal();
+ }
+ }
+
+ public boolean verifyTunerIsNull() {
+ synchronized (mLock) {
+ return mTuner == null;
+ }
+ }
+ };
+}
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
index 62fa719..998a1a3 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
@@ -20,9 +20,14 @@
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.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.media.tv.tuner.DemuxCapabilities;
import android.media.tv.tuner.Descrambler;
@@ -58,6 +63,8 @@
import android.media.tv.tuner.filter.SectionSettingsWithSectionBits;
import android.media.tv.tuner.filter.SectionSettingsWithTableInfo;
import android.media.tv.tuner.filter.Settings;
+import android.media.tv.tuner.filter.SharedFilter;
+import android.media.tv.tuner.filter.SharedFilterCallback;
import android.media.tv.tuner.filter.TemiEvent;
import android.media.tv.tuner.filter.TimeFilter;
import android.media.tv.tuner.filter.TlvFilterConfiguration;
@@ -91,6 +98,14 @@
import android.media.tv.tuner.frontend.IsdbtFrontendSettings;
import android.media.tv.tuner.frontend.OnTuneEventListener;
import android.media.tv.tuner.frontend.ScanCallback;
+import android.media.tv.tunerresourcemanager.TunerFrontendInfo;
+import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
+import android.media.tv.tunerresourcemanager.TunerResourceManager;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -100,15 +115,19 @@
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 java.time.Duration;
+import java.time.Instant;
import java.util.List;
+import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -119,11 +138,130 @@
public RequiredFeatureRule featureRule = new RequiredFeatureRule(
PackageManager.FEATURE_TUNER);
- private static final int TIMEOUT_MS = 10000;
+ private static final int TIMEOUT_MS = 10 * 1000; // 10 seconds
+ private static final int SCAN_TIMEOUT_MS = 2 * 60 * 1000; // 2 minutes
+ private static final long TIMEOUT_BINDER_SERVICE_SEC = 2;
private Context mContext;
private Tuner mTuner;
private CountDownLatch mLockLatch = new CountDownLatch(1);
+ private TunerResourceManager mTunerResourceManager = null;
+ private TestServiceConnection mConnection;
+ private ISharedFilterTestServer mSharedFilterTestServer;
+
+ private class TestServiceConnection implements ServiceConnection {
+ private BlockingQueue<IBinder> mBlockingQueue = new LinkedBlockingQueue<>();
+
+ public void onServiceConnected(ComponentName componentName, IBinder service) {
+ mBlockingQueue.offer(service);
+ }
+
+ public void onServiceDisconnected(ComponentName componentName) {}
+
+ public IBinder getService() throws Exception {
+ final IBinder service =
+ mBlockingQueue.poll(TIMEOUT_BINDER_SERVICE_SEC, TimeUnit.SECONDS);
+ return service;
+ }
+ }
+
+ private class TunerResourceTestServiceConnection implements ServiceConnection {
+ private BlockingQueue<IBinder> mBlockingQueue = new LinkedBlockingQueue<>();
+
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder service) {
+ mBlockingQueue.offer(service);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName){}
+
+ public ITunerResourceTestServer getService() throws Exception {
+ final IBinder service =
+ mBlockingQueue.poll(TIMEOUT_BINDER_SERVICE_SEC, TimeUnit.SECONDS);
+ return ITunerResourceTestServer.Stub.asInterface(service);
+ }
+ }
+
+ private class TunerTestOnTuneEventListener implements OnTuneEventListener {
+ public static final int INVALID_TUNE_EVENT = -1;
+ private static final int SLEEP_TIME_MS = 100;
+ private static final int TIMEOUT_MS = 500;
+ private final ReentrantLock mLock = new ReentrantLock();
+ private final ConditionVariable mCV = new ConditionVariable();
+ private int mLastTuneEvent = INVALID_TUNE_EVENT;
+
+ @Override
+ public void onTuneEvent(int tuneEvent) {
+ synchronized (mLock) {
+ mLastTuneEvent = tuneEvent;
+ mCV.open();
+ }
+ }
+
+ public void resetLastTuneEvent() {
+ synchronized (mLock) {
+ mLastTuneEvent = INVALID_TUNE_EVENT;
+ }
+ }
+
+ public int getLastTuneEvent() {
+ try {
+ // yield to let the callback handling execute
+ Thread.sleep(SLEEP_TIME_MS);
+ } catch (Exception e) {
+ // ignore exception
+ }
+ synchronized (mLock) {
+ mCV.block(TIMEOUT_MS);
+ mCV.close();
+ return mLastTuneEvent;
+ }
+ }
+ }
+
+ private class TunerTestLnbCallback implements LnbCallback {
+ public static final int INVALID_LNB_EVENT = -1;
+ private static final int SLEEP_TIME_MS = 100;
+ private static final int TIMEOUT_MS = 500;
+ private final ReentrantLock mDMLock = new ReentrantLock();
+ private final ConditionVariable mDMCV = new ConditionVariable();
+ private boolean mOnDiseqcMessageCalled = false;
+
+ // will not test this as there is no good way to trigger this
+ @Override
+ public void onEvent(int lnbEventType) {}
+
+ // will test this instead
+ @Override
+ public void onDiseqcMessage(byte[] diseqcMessage) {
+ synchronized (mDMLock) {
+ mOnDiseqcMessageCalled = true;
+ mDMCV.open();
+ }
+ }
+
+ public void resetOnDiseqcMessageCalled() {
+ synchronized (mDMLock) {
+ mOnDiseqcMessageCalled = false;
+ }
+ }
+
+ public boolean getOnDiseqcMessageCalled() {
+ try {
+ // yield to let the callback handling execute
+ Thread.sleep(SLEEP_TIME_MS);
+ } catch (Exception e) {
+ // ignore exception
+ }
+
+ synchronized (mDMLock) {
+ mDMCV.block(TIMEOUT_MS);
+ mDMCV.close();
+ return mOnDiseqcMessageCalled;
+ }
+ }
+ }
@Before
public void setUp() throws Exception {
@@ -131,6 +269,12 @@
InstrumentationRegistry
.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
mTuner = new Tuner(mContext, null, 100);
+
+ mConnection = new TestServiceConnection();
+ mContext.bindService(new Intent(mContext, SharedFilterTestService.class), mConnection,
+ Context.BIND_AUTO_CREATE);
+ mSharedFilterTestServer =
+ ISharedFilterTestServer.Stub.asInterface(mConnection.getService());
}
@After
@@ -139,6 +283,7 @@
mTuner.close();
mTuner = null;
}
+ mContext.unbindService(mConnection);
}
@Test
@@ -151,25 +296,87 @@
assertNotNull(mTuner);
int version = TunerVersionChecker.getTunerVersion();
assertTrue(version >= TunerVersionChecker.TUNER_VERSION_1_0);
- assertTrue(version <= TunerVersionChecker.TUNER_VERSION_1_1);
+ assertTrue(version <= TunerVersionChecker.TUNER_VERSION_2_0);
}
@Test
- public void testTuning() throws Exception {
+ public void testFrontendHardwareInfo() throws Exception {
+ String hwInfo = null;
+ try {
+ hwInfo = mTuner.getCurrentFrontendHardwareInfo();
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_2_0)) {
+ fail("Get Frontend hardware info should throw IllegalStateException.");
+ } else {
+ assertNull(hwInfo);
+ }
+ } catch (IllegalStateException e) {
+ // pass
+ }
+
List<Integer> ids = mTuner.getFrontendIds();
+ if (ids == null) return;
assertFalse(ids.isEmpty());
FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
int res = mTuner.tune(createFrontendSettings(info));
- assertEquals(Tuner.RESULT_SUCCESS, res);
- assertEquals(Tuner.RESULT_SUCCESS, mTuner.setLnaEnabled(false));
+ hwInfo = mTuner.getCurrentFrontendHardwareInfo();
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+ assertNotNull(hwInfo);
+ assertFalse(hwInfo.isEmpty());
+ } else {
+ assertNull(hwInfo);
+ }
res = mTuner.cancelTuning();
assertEquals(Tuner.RESULT_SUCCESS, res);
}
@Test
+ public void testTuning() throws Exception {
+ List<Integer> ids = mTuner.getFrontendIds();
+ if (ids == null) return;
+ assertFalse(ids.isEmpty());
+
+ FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+ int res = mTuner.tune(createFrontendSettings(info));
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+ res = mTuner.setLnaEnabled(false);
+ assertTrue((res == Tuner.RESULT_SUCCESS) || (res == Tuner.RESULT_UNAVAILABLE));
+ res = mTuner.cancelTuning();
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+ }
+
+ @Test
+ public void testMultiTuning() throws Exception {
+ List<Integer> ids = mTuner.getFrontendIds();
+ if (ids == null) return;
+ assertFalse(ids.isEmpty());
+
+ FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+ int res = mTuner.tune(createFrontendSettings(info));
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+ res = mTuner.cancelTuning();
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ // Tune again with the same frontend.
+ mTuner.tune(createFrontendSettings(info));
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+ res = mTuner.cancelTuning();
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ for (int i = 1; i < ids.size(); i++) {
+ FrontendInfo info2 = mTuner.getFrontendInfoById(ids.get(i));
+ if (info2.getType() != info.getType()) {
+ res = mTuner.tune(createFrontendSettings(info2));
+ assertEquals(Tuner.RESULT_INVALID_STATE, res);
+ }
+ }
+ }
+
+ @Test
public void testScanning() throws Exception {
List<Integer> ids = mTuner.getFrontendIds();
+ if (ids == null) return;
assertFalse(ids.isEmpty());
for (int id : ids) {
FrontendInfo info = mTuner.getFrontendInfoById(id);
@@ -181,7 +388,7 @@
getExecutor(),
getScanCallback());
assertEquals(Tuner.RESULT_SUCCESS, res);
- assertTrue(mLockLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue(mLockLatch.await(SCAN_TIMEOUT_MS, TimeUnit.MILLISECONDS));
res = mTuner.cancelScanning();
assertEquals(Tuner.RESULT_SUCCESS, res);
}
@@ -192,18 +399,17 @@
@Test
public void testFrontendStatus() throws Exception {
List<Integer> ids = mTuner.getFrontendIds();
+ if (ids == null) return;
assertFalse(ids.isEmpty());
for (int id : ids) {
- if (mTuner == null) {
- mTuner = new Tuner(mContext, null, 100);
- }
- FrontendInfo info = mTuner.getFrontendInfoById(id);
- int res = mTuner.tune(createFrontendSettings(info));
+ Tuner tuner = new Tuner(mContext, null, 100);
+ FrontendInfo info = tuner.getFrontendInfoById(id);
+ int res = tuner.tune(createFrontendSettings(info));
int[] statusCapabilities = info.getStatusCapabilities();
assertNotNull(statusCapabilities);
- FrontendStatus status = mTuner.getFrontendStatus(statusCapabilities);
+ FrontendStatus status = tuner.getFrontendStatus(statusCapabilities);
assertNotNull(status);
for (int i = 0; i < statusCapabilities.length; i++) {
@@ -263,6 +469,7 @@
status.getMer();
break;
case FrontendStatus.FRONTEND_STATUS_TYPE_FREQ_OFFSET:
+ status.getFreqOffsetLong();
status.getFreqOffset();
break;
case FrontendStatus.FRONTEND_STATUS_TYPE_HIERARCHY:
@@ -326,10 +533,22 @@
case FrontendStatus.FRONTEND_STATUS_TYPE_IS_SHORT_FRAMES_ENABLED:
status.isShortFramesEnabled();
break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_ISDBT_MODE:
+ status.getIsdbtMode();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG:
+ status.getIsdbtPartialReceptionFlag();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_STREAM_IDS:
+ status.getStreamIds();
+ break;
+ case FrontendStatus.FRONTEND_STATUS_TYPE_DVBT_CELL_IDS:
+ status.getDvbtCellIds();
+ break;
}
}
- mTuner.close();
- mTuner = null;
+ tuner.close();
+ tuner = null;
}
}
@@ -346,6 +565,57 @@
}
@Test
+ public void testLnbAddAndRemoveCallback() throws Exception {
+ TunerTestLnbCallback lnbCB1 = new TunerTestLnbCallback();
+ Lnb lnb = mTuner.openLnb(getExecutor(), lnbCB1);
+ if (lnb == null) {
+ return;
+ }
+
+ assertEquals(lnb.setVoltage(Lnb.VOLTAGE_5V), Tuner.RESULT_SUCCESS);
+ assertEquals(lnb.setTone(Lnb.TONE_NONE), Tuner.RESULT_SUCCESS);
+ assertEquals(
+ lnb.setSatellitePosition(Lnb.POSITION_A), Tuner.RESULT_SUCCESS);
+ lnb.sendDiseqcMessage(new byte[] {1, 2});
+ assertTrue(lnbCB1.getOnDiseqcMessageCalled());
+ lnbCB1.resetOnDiseqcMessageCalled();
+
+ List<Integer> ids = mTuner.getFrontendIds();
+ assertFalse(ids.isEmpty());
+ FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+ FrontendSettings feSettings = createFrontendSettings(info);
+ int res = mTuner.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ // create sharee
+ Tuner sharee = new Tuner(mContext, null, 100);
+ sharee.shareFrontendFromTuner(mTuner);
+ TunerTestLnbCallback lnbCB2 = new TunerTestLnbCallback();
+
+ // add it as sharee
+ lnb.addCallback(lnbCB2, getExecutor());
+
+ // check callback
+ lnb.sendDiseqcMessage(new byte[] {1, 2});
+ assertTrue(lnbCB1.getOnDiseqcMessageCalled());
+ lnbCB1.resetOnDiseqcMessageCalled();
+ assertTrue(lnbCB2.getOnDiseqcMessageCalled());
+ lnbCB2.resetOnDiseqcMessageCalled();
+
+ // remove sharee the sharee (should succeed)
+ assertTrue(lnb.removeCallback(lnbCB2));
+
+ // check callback (only the original owner gets callback
+ lnb.sendDiseqcMessage(new byte[] {1, 2});
+ assertTrue(lnbCB1.getOnDiseqcMessageCalled());
+ lnbCB1.resetOnDiseqcMessageCalled();
+ assertFalse(lnbCB2.getOnDiseqcMessageCalled());
+ lnbCB2.resetOnDiseqcMessageCalled();
+
+ sharee.close();
+ }
+
+ @Test
public void testOpenLnbByname() throws Exception {
Lnb lnb = mTuner.openLnbByName("default", getExecutor(), getLnbCallback());
if (lnb != null) {
@@ -367,6 +637,7 @@
public void testFrontendToCiCam() throws Exception {
// tune to get frontend resource
List<Integer> ids = mTuner.getFrontendIds();
+ if (ids == null) return;
assertFalse(ids.isEmpty());
FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
int res = mTuner.tune(createFrontendSettings(info));
@@ -393,9 +664,12 @@
// open filter to get demux resource
Filter f = mTuner.openFilter(
Filter.TYPE_TS, Filter.SUBTYPE_AUDIO, 1000, getExecutor(), getFilterCallback());
+ assertNotNull(f);
+ assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
Settings settings = AvSettings
.builder(Filter.TYPE_TS, true)
.setPassthrough(false)
+ .setUseSecureMemory(false)
.setAudioStreamType(AvSettings.AUDIO_STREAM_TYPE_MPEG1)
.build();
FilterConfiguration config = TsFilterConfiguration
@@ -441,6 +715,7 @@
// Tune a frontend before start the filter
List<Integer> ids = mTuner.getFrontendIds();
+ if (ids == null) return;
assertFalse(ids.isEmpty());
FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
@@ -467,6 +742,7 @@
Settings settings = AvSettings
.builder(Filter.TYPE_TS, true)
.setPassthrough(false)
+ .setUseSecureMemory(false)
.setAudioStreamType(AvSettings.AUDIO_STREAM_TYPE_MPEG1)
.build();
FilterConfiguration config = TsFilterConfiguration
@@ -478,6 +754,7 @@
// Tune a frontend before start the filter
List<Integer> ids = mTuner.getFrontendIds();
+ if (ids == null) return;
assertFalse(ids.isEmpty());
FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
@@ -509,7 +786,7 @@
public void testIpFilter() throws Exception {
Filter f = mTuner.openFilter(
Filter.TYPE_IP, Filter.SUBTYPE_IP, 1000, getExecutor(), getFilterCallback());
- assertNotNull(f);
+ if (f == null) return;
assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
FilterConfiguration config = IpFilterConfiguration
@@ -526,6 +803,7 @@
// Tune a frontend before start the filter
List<Integer> ids = mTuner.getFrontendIds();
+ if (ids == null) return;
assertFalse(ids.isEmpty());
FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
@@ -544,7 +822,7 @@
public void testAlpSectionFilterConfig() throws Exception {
Filter f = mTuner.openFilter(
Filter.TYPE_ALP, Filter.SUBTYPE_SECTION, 1000, getExecutor(), getFilterCallback());
- assertNotNull(f);
+ if (f == null) return;
assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
SectionSettingsWithSectionBits settings =
@@ -574,7 +852,7 @@
public void testMmtpPesFilterConfig() throws Exception {
Filter f = mTuner.openFilter(
Filter.TYPE_MMTP, Filter.SUBTYPE_PES, 1000, getExecutor(), getFilterCallback());
- assertNotNull(f);
+ if (f == null) return;
assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
PesSettings settings =
@@ -600,14 +878,22 @@
Filter f = mTuner.openFilter(
Filter.TYPE_MMTP, Filter.SUBTYPE_DOWNLOAD,
1000, getExecutor(), getFilterCallback());
- assertNotNull(f);
+ if (f == null) return;
assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
- DownloadSettings settings =
- DownloadSettings
- .builder(Filter.TYPE_MMTP)
- .setDownloadId(2)
- .build();
+ DownloadSettings.Builder builder = DownloadSettings.builder(Filter.TYPE_MMTP);
+ if (!TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+ builder.setUseDownloadId(true);
+ }
+ builder.setDownloadId(2);
+ DownloadSettings settings = builder.build();
+ if (!TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+ assertEquals(settings.useDownloadId(), true);
+ } else {
+ assertEquals(settings.useDownloadId(), false);
+ }
+ assertEquals(settings.getDownloadId(), 2);
+
MmtpFilterConfiguration config =
MmtpFilterConfiguration
.builder()
@@ -631,6 +917,7 @@
AvSettings
.builder(Filter.TYPE_TS, true) // is Audio
.setPassthrough(false)
+ .setUseSecureMemory(false)
.setAudioStreamType(AvSettings.AUDIO_STREAM_TYPE_MPEG1)
.build();
TsFilterConfiguration config =
@@ -677,7 +964,7 @@
public void testTlvTlvFilterConfig() throws Exception {
Filter f = mTuner.openFilter(
Filter.TYPE_TLV, Filter.SUBTYPE_TLV, 1000, getExecutor(), getFilterCallback());
- assertNotNull(f);
+ if (f == null) return;
assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
TlvFilterConfiguration config =
@@ -778,18 +1065,1255 @@
}
@Test
+ public void testResourceReclaimed() throws Exception {
+ List<Integer> ids = mTuner.getFrontendIds();
+ assertFalse(ids.isEmpty());
+ FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+ FrontendSettings feSettings = createFrontendSettings(info);
+
+ // first tune with mTuner to acquire resource
+ int res = mTuner.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+ assertNotNull(mTuner.getFrontendInfo());
+
+ // now tune with a higher priority tuner to have mTuner's resource reclaimed
+ Tuner higherPrioTuner = new Tuner(mContext, null, 200);
+ res = higherPrioTuner.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+ assertNotNull(higherPrioTuner.getFrontendInfo());
+
+ higherPrioTuner.close();
+ }
+
+ // TODO: change this to use ITunerResourceTestServer
+ @Test
+ public void testResourceReclaimedDifferentThread() throws Exception {
+ List<Integer> ids = mTuner.getFrontendIds();
+ assertFalse(ids.isEmpty());
+ FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+ FrontendSettings feSettings = createFrontendSettings(info);
+
+ // first tune with mTuner to acquire resource
+ int res = mTuner.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+ assertNotNull(mTuner.getFrontendInfo());
+
+ // now tune with a higher priority tuner to have mTuner's resource reclaimed
+ TunerHandler tunerHandler = createTunerHandler(null);
+ Message msgCreate = new Message();
+ msgCreate.what = MSG_TUNER_HANDLER_CREATE;
+ msgCreate.arg1 = 200;
+ tunerHandler.sendMessage(msgCreate);
+ mTunerHandlerTaskComplete.block();
+ mTunerHandlerTaskComplete.close();
+
+ Message msgTune = new Message();
+ msgTune.what = MSG_TUNER_HANDLER_TUNE;
+ msgTune.obj = (Object) feSettings;
+ tunerHandler.sendMessage(msgTune);
+
+ // call mTuner.close in parallel
+ int sleepMS = 1;
+ //int sleepMS = (int) (Math.random() * 3.);
+ try {
+ Thread.sleep(sleepMS);
+ } catch (Exception e) { } // ignore
+ mTuner.close();
+ mTuner = null;
+
+ mTunerHandlerTaskComplete.block();
+ mTunerHandlerTaskComplete.close();
+ res = tunerHandler.getResult();
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ Tuner higherPrioTuner = tunerHandler.getTuner();
+ assertNotNull(higherPrioTuner.getFrontendInfo());
+
+ Message msgClose = new Message();
+ msgClose.what = MSG_TUNER_HANDLER_CLOSE;
+ tunerHandler.sendMessage(msgClose);
+
+ }
+
+ @Test
+ public void testResourceReclaimedDifferentProcess() throws Exception {
+ List<Integer> ids = mTuner.getFrontendIds();
+ int frontendIndex = 0;
+ assertFalse(ids.isEmpty());
+ FrontendInfo info = mTuner.getFrontendInfoById(ids.get(frontendIndex));
+ FrontendSettings feSettings = createFrontendSettings(info);
+
+ // set up the test server
+ TunerResourceTestServiceConnection connection = new TunerResourceTestServiceConnection();
+ ITunerResourceTestServer tunerResourceTestServer = null;
+ Intent intent = new Intent(mContext, TunerResourceTestService.class);
+
+ // get the TunerResourceTestService
+ mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+ tunerResourceTestServer = connection.getService();
+
+ // CASE1 - normal reclaim
+ //
+ // first tune with mTuner to acquire resource
+ int res = mTuner.tune(feSettings);
+ boolean tunerReclaimed = false;
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+ assertNotNull(mTuner.getFrontendInfo());
+
+ // now tune with a higher priority tuner to have mTuner's resource reclaimed
+
+ // create higher priority tuner
+ tunerResourceTestServer.createTuner(200);
+
+ // now tune on higher priority tuner to get mTuner reclaimed
+ res = tunerResourceTestServer.tune(frontendIndex);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ try {
+ int[] statusCapabilities = info.getStatusCapabilities();
+ mTuner.getFrontendStatus(statusCapabilities);
+
+ } catch (IllegalStateException e) {
+ tunerReclaimed = true;
+ mTuner.close();
+ mTuner = null;
+ }
+
+ // confirm if the mTuner is reclaimed
+ assertTrue(tunerReclaimed);
+
+ tunerResourceTestServer.closeTuner();
+ assertTrue(tunerResourceTestServer.verifyTunerIsNull());
+
+
+ // CASE2 - race between Tuner#close() and reclaim
+ mTuner = new Tuner(mContext, null, 100);
+ res = mTuner.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+ assertNotNull(mTuner.getFrontendInfo());
+
+ tunerResourceTestServer.createTuner(200);
+ tunerResourceTestServer.tuneAsync(frontendIndex);
+
+ // adjust timing to induce race/deadlock
+ int sleepMS = 4;
+ //int sleepMS = (int) (Math.random() * 5.);
+ try {
+ Thread.sleep(sleepMS);
+ } catch (Exception e) { } // ignore
+ mTuner.close();
+ mTuner = null;
+
+ tunerResourceTestServer.closeTuner();
+
+ // unbind
+ mContext.unbindService(connection);
+ }
+
+ @Test
public void testShareFrontendFromTuner() throws Exception {
+ Tuner tuner100 = new Tuner(mContext, null, 100);
+ List<Integer> ids = tuner100.getFrontendIds();
+ assertFalse(ids.isEmpty());
+ FrontendInfo info = tuner100.getFrontendInfoById(ids.get(0));
+ FrontendSettings feSettings = createFrontendSettings(info);
+ int[] statusTypes = {1};
+ boolean exceptionThrown = false;
+ int res;
+
+ // CASE1: check resource reclaim while sharee's priority < owner's priority
+ // let tuner100 share from tuner200
+ Tuner tuner200 = new Tuner(mContext, null, 200);
+ res = tuner200.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ info = tuner200.getFrontendInfoById(ids.get(0));
+ res = tuner200.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ tuner100 = new Tuner(mContext, null, 100);
+ tuner100.shareFrontendFromTuner(tuner200);
+ // call openFilter to trigger ITunerDemux.setFrontendDataSourceById()
+ Filter f = tuner100.openFilter(
+ Filter.TYPE_TS, Filter.SUBTYPE_SECTION, 1000, getExecutor(), getFilterCallback());
+ assertNotNull(f);
+
+ // setup onTuneCallback
+ TunerTestOnTuneEventListener cb100 = new TunerTestOnTuneEventListener();
+ TunerTestOnTuneEventListener cb200 = new TunerTestOnTuneEventListener();
+
+ // tune again on the owner
+ info = tuner200.getFrontendInfoById(ids.get(1));
+ tuner100.setOnTuneEventListener(getExecutor(), cb100);
+ tuner200.setOnTuneEventListener(getExecutor(), cb200);
+ res = tuner200.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+ assertEquals(OnTuneEventListener.SIGNAL_LOCKED, cb100.getLastTuneEvent());
+ assertEquals(OnTuneEventListener.SIGNAL_LOCKED, cb200.getLastTuneEvent());
+ tuner100.clearOnTuneEventListener();
+ tuner200.clearOnTuneEventListener();
+
+ // now let the higher priority tuner steal the resource
+ Tuner tuner300 = new Tuner(mContext, null, 300);
+ res = tuner300.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ // confirm owner & sharee's resource gets reclaimed by confirming an exception is thrown
+ exceptionThrown = false;
+ try {
+ tuner200.getFrontendStatus(statusTypes);
+ } catch (Exception e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+
+ exceptionThrown = false;
+ try {
+ tuner100.getFrontendStatus(statusTypes);
+ } catch (Exception e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+
+ tuner100.close();
+ tuner200.close();
+ tuner300.close();
+
+
+ // CASE2: check resource reclaim fail when sharee's priority > new requester
+ tuner100 = new Tuner(mContext, null, 100);
+ res = tuner100.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ tuner300 = new Tuner(mContext, null, 300);
+ tuner300.shareFrontendFromTuner(tuner100);
+ f = tuner100.openFilter(
+ Filter.TYPE_TS, Filter.SUBTYPE_SECTION, 1000, getExecutor(), getFilterCallback());
+ assertNotNull(f);
+
+ tuner200 = new Tuner(mContext, null, 200);
+ res = tuner200.tune(feSettings);
+ assertNotEquals(Tuner.RESULT_SUCCESS, res);
+
+ // confirm the original tuner is still intact
+ res = tuner100.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ tuner100.close();
+ tuner200.close();
+ tuner300.close();
+ }
+
+ private void testTransferFeOwnershipSingleTuner() {
+ List<Integer> ids = mTuner.getFrontendIds();
+ if (ids == null) {
+ return;
+ }
+ assertFalse(ids.isEmpty());
+ FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+ FrontendSettings feSettings = createFrontendSettings(info);
+
+ // SCENARIO 1 - transfer and close the previous owner
+
+ // First create a tuner and tune() to acquire frontend resource
+ Tuner tunerA = new Tuner(mContext, null, 100);
+ int res = tunerA.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ // Create another tuner and share frontend from tunerA
+ Tuner tunerB = new Tuner(mContext, null, 500);
+ tunerB.shareFrontendFromTuner(tunerA);
+ DvrRecorder d = tunerB.openDvrRecorder(100, getExecutor(), getRecordListener());
+ assertNotNull(d);
+
+ // Call transferOwner in the wrong configurations and confirm it fails
+ assertEquals(Tuner.RESULT_INVALID_STATE, tunerB.transferOwner(tunerA));
+ Tuner nonSharee = new Tuner(mContext, null, 300);
+ assertEquals(Tuner.RESULT_INVALID_STATE, tunerA.transferOwner(nonSharee));
+ nonSharee.close();
+
+ // Now call it correctly to transfer ownership from tunerA to tunerB
+ assertEquals(Tuner.RESULT_SUCCESS, tunerA.transferOwner(tunerB));
+
+ // Close the original owner (tunerA)
+ tunerA.close();
+
+ // Confirm the new owner (tunerB) is still functional
+ assertNotNull(tunerB.getFrontendInfo());
+
+ // Close the new owner (tunerB)
+ d.close();
+ tunerB.close();
+
+ // SCENARIO 2 - transfer and closeFrontend and tune on the previous owner
+
+ // First create a tuner and tune() to acquire frontend resource
+ tunerA = new Tuner(mContext, null, 200);
+ res = tunerA.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ // Create another tuner and share frontend from tunerA
+ tunerB = new Tuner(mContext, null, 100);
+ tunerB.shareFrontendFromTuner(tunerA);
+ assertNotNull(tunerB.getFrontendInfo());
+
+ // Transfer ownership from tunerA to tunerB
+ assertEquals(Tuner.RESULT_SUCCESS, tunerA.transferOwner(tunerB));
+
+ // Close frontend for the original owner (tunerA)
+ tunerA.closeFrontend();
+
+ // Confirm tune works without going through Tuner.close() even after transferOwner()
+ // The purpose isn't to get tunerB's frontend revoked, but doing so as singletuner
+ // based test has wider coverage
+ res = tunerA.tune(feSettings); // this should reclaim tunerB
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ // Confirm tuberB is revoked
+ assertNull(tunerB.getFrontendInfo());
+
+ // Close tunerA
+ tunerA.close();
+
+ // close TunerB just in case
+ tunerB.close();
+ }
+
+ private void testTransferFeAndCiCamOwnership() {
+ List<Integer> ids = mTuner.getFrontendIds();
+ assertNotNull(ids);
+ assertFalse(ids.isEmpty());
+ FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+ FrontendSettings feSettings = createFrontendSettings(info);
+
+ // Create tuner and tune to get frontend resource
+ Tuner tunerA = new Tuner(mContext, null, 100);
+ assertEquals(Tuner.RESULT_SUCCESS, tunerA.tune(feSettings));
+
+ int ciCamId = 0;
+ boolean linkCiCamToFrontendSupported = false;
+
+ // connect CiCam to Frontend
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+ // TODO: get real CiCam id from MediaCas
+ assertEquals(Tuner.RESULT_SUCCESS, tunerA.connectFrontendToCiCam(ciCamId));
+ linkCiCamToFrontendSupported = true;
+ } else {
+ assertEquals(Tuner.INVALID_LTS_ID, tunerA.connectFrontendToCiCam(ciCamId));
+ }
+
+ // connect CiCam to Demux
+ assertEquals(Tuner.RESULT_SUCCESS, tunerA.connectCiCam(ciCamId));
+
+ // start another tuner and connect the same CiCam to its own demux
+ Tuner tunerB = new Tuner(mContext, null, 400);
+ tunerB.shareFrontendFromTuner(tunerA);
+ assertNotNull(tunerB.getFrontendInfo());
+ assertEquals(Tuner.RESULT_SUCCESS, tunerB.connectCiCam(ciCamId));
+
+ // unlink CiCam to Demux in tunerA and transfer ownership
+ assertEquals(Tuner.RESULT_SUCCESS, tunerA.disconnectCiCam());
+ assertEquals(Tuner.RESULT_SUCCESS, tunerA.transferOwner(tunerB));
+
+ // close the original owner
+ tunerA.close();
+
+ // disconnect CiCam from demux
+ assertEquals(Tuner.RESULT_SUCCESS, tunerB.disconnectCiCam());
+
+ // let Tuner.close() handle the release of CiCam
+ tunerB.close();
+
+ // now that the CiCam is released, disconnectFrontendToCiCam() should fail
+ assertEquals(Tuner.RESULT_UNAVAILABLE, tunerB.disconnectFrontendToCiCam(ciCamId));
+
+ // see if tune still works just in case
+ tunerA = new Tuner(mContext, null, 100);
+ assertEquals(Tuner.RESULT_SUCCESS, tunerA.tune(feSettings));
+ tunerA.close();
+ }
+
+ private void testTransferFeAndLnbOwnership() {
+ List<Integer> ids = mTuner.getFrontendIds();
+ assertNotNull(ids);
+ assertFalse(ids.isEmpty());
+ FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+ FrontendSettings feSettings = createFrontendSettings(info);
+
+ // Create tuner and tune to acquire frontend resource
+ Tuner tunerA = new Tuner(mContext, null, 100);
+ assertEquals(Tuner.RESULT_SUCCESS, tunerA.tune(feSettings));
+
+ // Open Lnb and check the callback
+ TunerTestLnbCallback lnbCB1 = new TunerTestLnbCallback();
+ Lnb lnbA = tunerA.openLnb(getExecutor(), lnbCB1);
+ assertNotNull(lnbA);
+ lnbA.setVoltage(Lnb.VOLTAGE_5V);
+ lnbA.setTone(Lnb.TONE_CONTINUOUS);
+ lnbA.sendDiseqcMessage(new byte[] {1, 2});
+ assertTrue(lnbCB1.getOnDiseqcMessageCalled());
+ lnbCB1.resetOnDiseqcMessageCalled();
+
+ // Create another tuner and share from tunerB
+ Tuner tunerB = new Tuner(mContext, null, 300);
+ tunerB.shareFrontendFromTuner(tunerA);
+
+ // add sharee and check the callback
+ TunerTestLnbCallback lnbCB2 = new TunerTestLnbCallback();
+ lnbA.addCallback(lnbCB2, getExecutor());
+ lnbA.sendDiseqcMessage(new byte[] {1, 2});
+ assertTrue(lnbCB1.getOnDiseqcMessageCalled());
+ lnbCB1.resetOnDiseqcMessageCalled();
+ assertTrue(lnbCB2.getOnDiseqcMessageCalled());
+ lnbCB2.resetOnDiseqcMessageCalled();
+
+ // transfer owner and check callback
+ assertEquals(Tuner.RESULT_SUCCESS, tunerA.transferOwner(tunerB));
+ lnbA.sendDiseqcMessage(new byte[] {1, 2});
+ assertTrue(lnbCB1.getOnDiseqcMessageCalled());
+ lnbCB1.resetOnDiseqcMessageCalled();
+ assertTrue(lnbCB2.getOnDiseqcMessageCalled());
+ lnbCB2.resetOnDiseqcMessageCalled();
+
+ // remove the owner callback (just for testing)
+ assertTrue(lnbA.removeCallback(lnbCB2));
+
+ // remove sharee and check callback
+ assertTrue(lnbA.removeCallback(lnbCB1));
+ lnbA.sendDiseqcMessage(new byte[] {1, 2});
+ assertFalse(lnbCB1.getOnDiseqcMessageCalled());
+ lnbCB1.resetOnDiseqcMessageCalled();
+ assertFalse(lnbCB2.getOnDiseqcMessageCalled());
+ lnbCB2.resetOnDiseqcMessageCalled();
+
+ // close the original owner
+ tunerA.close();
+
+ // confirm the new owner is still intact
+ int[] statusCapabilities = info.getStatusCapabilities();
+ assertNotNull(statusCapabilities);
+ FrontendStatus status = tunerB.getFrontendStatus(statusCapabilities);
+ assertNotNull(status);
+
+ tunerB.close();
+ }
+
+ @Test
+ public void testTransferOwner() throws Exception {
+ testTransferFeOwnershipSingleTuner();
+ testTransferFeAndCiCamOwnership();
+ testTransferFeAndLnbOwnership();
+ }
+
+ @Test
+ public void testClose() throws Exception {
Tuner other = new Tuner(mContext, null, 100);
+
List<Integer> ids = other.getFrontendIds();
+ if (ids == null) return;
assertFalse(ids.isEmpty());
FrontendInfo info = other.getFrontendInfoById(ids.get(0));
- // call tune() to open frontend resource
- int res = other.tune(createFrontendSettings(info));
+ FrontendSettings feSettings = createFrontendSettings(info);
+ int res = other.tune(feSettings);
assertEquals(Tuner.RESULT_SUCCESS, res);
assertNotNull(other.getFrontendInfo());
- mTuner.shareFrontendFromTuner(other);
+
other.close();
+
+ // make sure pre-existing tuner is still functional
+ res = mTuner.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+ assertNotNull(mTuner.getFrontendInfo());
+
+ // Frontend sharing scenario 1: close owner first
+ // create sharee
+ Tuner sharee = new Tuner(mContext, null, 100);
+ sharee.shareFrontendFromTuner(mTuner);
+
+ // close the owner
+ mTuner.close();
+ mTuner = null;
+
+ // check the sharee is also closed
+ // tune() would have failed even before close() but still..
+ // TODO: fix this once callback sharing is implemented
+ res = sharee.tune(feSettings);
+ assertEquals(Tuner.RESULT_UNAVAILABLE, res);
+
+ sharee.close();
+
+ // Frontend sharing scenario 2: close sharee first
+ // create owner first
+ mTuner = new Tuner(mContext, null, 100);
+ res = mTuner.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ // create sharee
+ sharee = new Tuner(mContext, null, 100);
+ sharee.shareFrontendFromTuner(mTuner);
+
+ // close sharee
+ sharee.close();
+
+ // confirm owner is still intact
+ int[] statusCapabilities = info.getStatusCapabilities();
+ assertNotNull(statusCapabilities);
+ FrontendStatus status = mTuner.getFrontendStatus(statusCapabilities);
+ assertNotNull(status);
+
+ }
+
+ @Test
+ public void testCloseFrontend() throws Exception {
+ List<Integer> ids = mTuner.getFrontendIds();
+ if (ids == null) {
+ return;
+ }
+
+ // SCENARIO 1 - without Lnb
+ assertFalse(ids.isEmpty());
+ FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+ FrontendSettings feSettings = createFrontendSettings(info);
+ int res = mTuner.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+ assertNotNull(mTuner.getFrontendInfo());
+
+ // now close frontend
+ mTuner.closeFrontend();
+
+ // confirm frontend is closed
+ int[] statusCapabilities = info.getStatusCapabilities();
+ boolean frontendClosed = false;
+ try {
+ mTuner.getFrontendStatus(statusCapabilities);
+
+ } catch (IllegalStateException e) {
+ frontendClosed = true;
+ }
+ assertTrue(frontendClosed);
+
+ // now tune to a different setting
+ info = mTuner.getFrontendInfoById(ids.get(1));
+ feSettings = createFrontendSettings(info);
+ mTuner.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+ assertNotNull(mTuner.getFrontendInfo());
+ FrontendStatus status = mTuner.getFrontendStatus(statusCapabilities);
+ assertNotNull(status);
+
+ // SCENARIO 2 - with Lnb
+
+ TunerTestLnbCallback lnbCB1 = new TunerTestLnbCallback();
+ Lnb lnb = mTuner.openLnb(getExecutor(), lnbCB1);
+ if (lnb == null) {
+ return;
+ }
+
+ mTuner.closeFrontend();
+ // confirm frontend is closed
+ statusCapabilities = info.getStatusCapabilities();
+ frontendClosed = false;
+ try {
+ mTuner.getFrontendStatus(statusCapabilities);
+
+ } catch (IllegalStateException e) {
+ frontendClosed = true;
+ }
+ assertTrue(frontendClosed);
+
+ info = mTuner.getFrontendInfoById(ids.get(0));
+ feSettings = createFrontendSettings(info);
+ mTuner.tune(feSettings);
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+ assertNotNull(mTuner.getFrontendInfo());
+ status = mTuner.getFrontendStatus(statusCapabilities);
+ assertNotNull(status);
+ }
+
+ @Test
+ public void testHasUnusedFrontend1() throws Exception {
+ prepTRMCustomFeResourceMapTest();
+
+ // Use try block to ensure restoring the TunerResourceManager
+ // Note: the handles will be changed from the original value, but should be OK
+ try {
+ TunerFrontendInfo[] infos = new TunerFrontendInfo[6];
+ // tunerFrontendInfo(handle, FrontendSettings.TYPE_*, exclusiveGroupId
+ infos[0] = tunerFrontendInfo(1, FrontendSettings.TYPE_DVBT, 1);
+ infos[1] = tunerFrontendInfo(2, FrontendSettings.TYPE_DVBC, 1);
+ infos[2] = tunerFrontendInfo(3, FrontendSettings.TYPE_DVBS, 1);
+ infos[3] = tunerFrontendInfo(4, FrontendSettings.TYPE_DVBT, 2);
+ infos[4] = tunerFrontendInfo(5, FrontendSettings.TYPE_DVBC, 2);
+ infos[5] = tunerFrontendInfo(6, FrontendSettings.TYPE_DVBS, 2);
+
+ mTunerResourceManager.setFrontendInfoList(infos);
+
+ Tuner A = new Tuner(mContext, null, 100);
+ Tuner B = new Tuner(mContext, null, 100);
+ Tuner C = new Tuner(mContext, null, 100);
+
+ // check before anyone holds resource
+ assertFalse(A.hasUnusedFrontend(FrontendSettings.TYPE_UNDEFINED));
+ assertFalse(A.hasUnusedFrontend(FrontendSettings.TYPE_ATSC));
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBC));
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBS));
+
+ // let B hold resource
+ assignFeResource(B.getClientId(), FrontendSettings.TYPE_DVBT,
+ true /* expectedResult */, 1 /* expectedHandle */);
+
+ // check when one of the two exclusive groups are held
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBC));
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBS));
+
+ assertTrue(B.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+
+ // let C hold the resource
+ assignFeResource(C.getClientId(), FrontendSettings.TYPE_DVBC,
+ true /* expectedResult */, 5 /* expectedHandle */);
+
+ assertFalse(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+ assertFalse(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBC));
+ assertFalse(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBS));
+
+ assertFalse(B.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+ assertFalse(C.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+
+ // let go of B's resource
+ B.close();
+
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBC));
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBS));
+
+ assertTrue(B.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+ assertTrue(C.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+
+ C.close();
+ A.close();
+ } catch (Exception e) {
+ throw (e);
+ } finally {
+ cleanupTRMCustomFeResourceMapTest();
+ }
+ }
+
+ @Test
+ public void testHasUnusedFrontend2() throws Exception {
+ prepTRMCustomFeResourceMapTest();
+
+ // Use try block to ensure restoring the TunerResourceManager
+ // Note: the handles will be changed from the original value, but should be OK
+ try {
+ TunerFrontendInfo[] infos = new TunerFrontendInfo[5];
+ // tunerFrontendInfo(handle, FrontendSettings.TYPE_*, exclusiveGroupId
+ infos[0] = tunerFrontendInfo(1, FrontendSettings.TYPE_DVBT, 1);
+ infos[1] = tunerFrontendInfo(2, FrontendSettings.TYPE_DVBC, 1);
+ infos[2] = tunerFrontendInfo(3, FrontendSettings.TYPE_DVBT, 2);
+ infos[3] = tunerFrontendInfo(4, FrontendSettings.TYPE_DVBC, 2);
+ infos[4] = tunerFrontendInfo(5, FrontendSettings.TYPE_DVBS, 3);
+
+ mTunerResourceManager.setFrontendInfoList(infos);
+
+ Tuner A = new Tuner(mContext, null, 100);
+ Tuner B = new Tuner(mContext, null, 100);
+ Tuner C = new Tuner(mContext, null, 100);
+
+ // let B hold resource
+ assignFeResource(B.getClientId(), FrontendSettings.TYPE_DVBT,
+ true /* expectedResult */, 1 /* expectedHandle */);
+
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBC));
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBS));
+
+ // let C hold the resource
+ assignFeResource(C.getClientId(), FrontendSettings.TYPE_DVBC,
+ true /* expectedResult */, 4 /* expectedHandle */);
+
+ assertFalse(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+ assertFalse(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBC));
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBS));
+
+ B.close();
+ C.close();
+ } catch (Exception e) {
+ throw (e);
+ } finally {
+ cleanupTRMCustomFeResourceMapTest();
+ }
+ }
+
+ @Test
+ public void testHasUnusedFrontend3() throws Exception {
+ prepTRMCustomFeResourceMapTest();
+
+ // Use try block to ensure restoring the TunerResourceManager
+ // Note: the handles will be changed from the original value, but should be OK
+ try {
+ TunerFrontendInfo[] infos = new TunerFrontendInfo[6];
+ // tunerFrontendInfo(handle, FrontendSettings.TYPE_*, exclusiveGroupId
+ infos[0] = tunerFrontendInfo(1, FrontendSettings.TYPE_DVBT, 1);
+ infos[1] = tunerFrontendInfo(2, FrontendSettings.TYPE_DVBC, 1);
+ infos[2] = tunerFrontendInfo(3, FrontendSettings.TYPE_DVBS, 1);
+ infos[3] = tunerFrontendInfo(4, FrontendSettings.TYPE_DVBT, 2);
+ infos[4] = tunerFrontendInfo(5, FrontendSettings.TYPE_DVBC, 2);
+ infos[5] = tunerFrontendInfo(6, FrontendSettings.TYPE_DVBS, 2);
+
+ mTunerResourceManager.setFrontendInfoList(infos);
+
+ Tuner A = new Tuner(mContext, null, 100);
+ Tuner B = new Tuner(mContext, null, 100);
+ Tuner C = new Tuner(mContext, null, 100);
+
+ // let B hold resource
+ assignFeResource(B.getClientId(), FrontendSettings.TYPE_DVBT,
+ true /* expectedResult */, 1 /* expectedHandle */);
+
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBC));
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBS));
+
+ // let C share from B
+ mTunerResourceManager.shareFrontend(C.getClientId(), B.getClientId());
+
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBC));
+ assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBS));
+
+ A.close();
+ C.close();
+ B.close();
+ } catch (Exception e) {
+ throw (e);
+ } finally {
+ cleanupTRMCustomFeResourceMapTest();
+ }
+ }
+
+ @Test
+ public void testIsLowestPriorityCornerCases() throws Exception {
+ prepTRMCustomFeResourceMapTest();
+
+ // Use try block to ensure restoring the TunerResourceManager
+ // Note: the handles will be changed from the original value, but should be OK
+ try {
+ setupSingleTunerSetupForIsLowestPriority();
+
+ // must return true when non existing frontend type is specified
+ assertTrue(mTuner.isLowestPriority(FrontendSettings.TYPE_UNDEFINED));
+ assertTrue(mTuner.isLowestPriority(FrontendSettings.TYPE_ATSC));
+
+ // must return true when no one is holding the resource
+ assertTrue(mTuner.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ assertTrue(mTuner.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertTrue(mTuner.isLowestPriority(FrontendSettings.TYPE_DVBT));
+
+ // must return true when the callee is the only one holding the resource
+ assignFeResource(mTuner.getClientId(), FrontendSettings.TYPE_DVBT,
+ true /* expectedResult */, 1 /* expectedHandle */);
+ assertTrue(mTuner.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ assertTrue(mTuner.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertTrue(mTuner.isLowestPriority(FrontendSettings.TYPE_DVBT));
+
+ } catch (Exception e) {
+ throw (e);
+ } finally {
+ cleanupTRMCustomFeResourceMapTest();
+ }
+ }
+
+ @Test
+ public void testIsLowestPriorityTwoClients() throws Exception {
+ prepTRMCustomFeResourceMapTest();
+
+ // Use try block to ensure restoring the TunerResourceManager
+ // Note: the handles will be changed from the original value, but should be OK
+ try {
+ setupSingleTunerSetupForIsLowestPriority();
+ testTwoClientsForIsLowestPriority(200, 100); // A > B
+ testTwoClientsForIsLowestPriority(100, 200); // A < B
+ testTwoClientsForIsLowestPriority(100, 100); // A = B
+
+ setupDualTunerSetupForIsLowestPriority();
+ testTwoClientsForIsLowestPriority(200, 100); // A > B
+ testTwoClientsForIsLowestPriority(100, 200); // A < B
+ testTwoClientsForIsLowestPriority(100, 100); // A = B
+ } catch (Exception e) {
+ throw (e);
+ } finally {
+ cleanupTRMCustomFeResourceMapTest();
+ }
+ }
+
+ @Test
+ public void testIsLowestPriorityThreeClients() throws Exception {
+ prepTRMCustomFeResourceMapTest();
+
+ // Use try block to ensure restoring the TunerResourceManager
+ // Note: the handles will be changed from the original value, but should be OK
+ try {
+ setupDualTunerSetupForIsLowestPriority();
+ testThreeClientsForIsLowestPriority(300, 200, 100); // A > B > C
+ testThreeClientsForIsLowestPriority(300, 100, 200); // A > C > B
+ testThreeClientsForIsLowestPriority(200, 300, 100); // B > A > C
+ testThreeClientsForIsLowestPriority(200, 100, 300); // C > A > B
+ testThreeClientsForIsLowestPriority(100, 300, 200); // B > C > A
+ testThreeClientsForIsLowestPriority(100, 200, 300); // C > B > A
+ testThreeClientsForIsLowestPriority(100, 100, 100); // A = B = C
+ testThreeClientsForIsLowestPriority(200, 200, 100); // A = B > C
+ testThreeClientsForIsLowestPriority(200, 100, 100); // A > B = C
+ testThreeClientsForIsLowestPriority(200, 100, 200); // A = C > B
+ testThreeClientsForIsLowestPriority(200, 300, 200); // B > A = C
+ testThreeClientsForIsLowestPriority(100, 100, 200); // C > A = B
+ testThreeClientsForIsLowestPriority(100, 200, 200); // B = C > A
+ } catch (Exception e) {
+ throw (e);
+ } finally {
+ cleanupTRMCustomFeResourceMapTest();
+ }
+ }
+
+ private TunerFrontendInfo tunerFrontendInfo(
+ int handle, int frontendType, int exclusiveGroupId) {
+ TunerFrontendInfo info = new TunerFrontendInfo();
+ info.handle = handle;
+ info.type = frontendType;
+ info.exclusiveGroupId = exclusiveGroupId;
+ return info;
+ }
+
+ /**
+ * Prep function for TunerTest that requires custom frontend resource map
+ */
+ private void prepTRMCustomFeResourceMapTest() {
+ if (mTunerResourceManager == null) {
+ mTunerResourceManager = (TunerResourceManager)
+ mContext.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
+ }
+ mTunerResourceManager.storeResourceMap(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
+ mTunerResourceManager.clearResourceMap(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
+ }
+
+ /**
+ * Clean up function for TunerTest that requires custom frontend resource map
+ */
+ private void cleanupTRMCustomFeResourceMapTest() {
+ // first close mTuner in case a frontend resource is opened
+ if (mTuner != null) {
+ mTuner.close();
+ mTuner = null;
+ }
+
+ // now restore the original frontend resource map
+ if (mTunerResourceManager != null) {
+ mTunerResourceManager.restoreResourceMap(
+ TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
+ }
+ }
+
+ private void clearFrontendInfoList() {
+ if (mTunerResourceManager != null) {
+ mTunerResourceManager.clearResourceMap(
+ TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
+ }
+ }
+
+ private void assignFeResource(int clientId, int frontendType,
+ boolean expectedResult, int expectedHandle) {
+ int[] feHandle = new int[1];
+ TunerFrontendRequest request = new TunerFrontendRequest();
+ request.clientId = clientId;
+ request.frontendType = frontendType;
+ boolean granted = mTunerResourceManager.requestFrontend(request, feHandle);
+ assertEquals(granted, expectedResult);
+ assertEquals(feHandle[0], expectedHandle);
+ }
+
+ private void setupSingleTunerSetupForIsLowestPriority() {
+ // first clear the frontend resource to register new set of resources
+ clearFrontendInfoList();
+
+ TunerFrontendInfo[] infos = new TunerFrontendInfo[3];
+ // tunerFrontendInfo(handle, FrontendSettings.TYPE_*, exclusiveGroupId
+ infos[0] = tunerFrontendInfo(1, FrontendSettings.TYPE_DVBT, 1);
+ infos[1] = tunerFrontendInfo(2, FrontendSettings.TYPE_DVBC, 1);
+ infos[2] = tunerFrontendInfo(3, FrontendSettings.TYPE_DVBS, 1);
+
+ mTunerResourceManager.setFrontendInfoList(infos);
+ }
+
+ private void setupDualTunerSetupForIsLowestPriority() {
+ // first clear the frontend resource to register new set of resources
+ clearFrontendInfoList();
+
+ TunerFrontendInfo[] infos = new TunerFrontendInfo[6];
+ // tunerFrontendInfo(handle, FrontendSettings.TYPE_*, exclusiveGroupId
+ infos[0] = tunerFrontendInfo(1, FrontendSettings.TYPE_DVBT, 1);
+ infos[1] = tunerFrontendInfo(2, FrontendSettings.TYPE_DVBC, 1);
+ infos[2] = tunerFrontendInfo(3, FrontendSettings.TYPE_DVBS, 1);
+ infos[3] = tunerFrontendInfo(4, FrontendSettings.TYPE_DVBT, 2);
+ infos[4] = tunerFrontendInfo(5, FrontendSettings.TYPE_DVBC, 2);
+ infos[5] = tunerFrontendInfo(6, FrontendSettings.TYPE_DVBS, 2);
+
+ mTunerResourceManager.setFrontendInfoList(infos);
+ }
+
+
+ private void testTwoClientsForIsLowestPriority(int prioA, int prioB) {
+
+ Tuner A = new Tuner(mContext, null, prioA);
+ Tuner B = new Tuner(mContext, null, prioB);
+
+ // all should return true
+ assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBC));
+
+ // let A hold resource
+ assignFeResource(A.getClientId(), FrontendSettings.TYPE_DVBT,
+ true /* expectedResult */, 1 /* expectedHandle */);
+
+ // should return true for A as A is the sole holder
+ assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ // should return false for B only if A < B
+ if ( prioA < prioB ) {
+ assertFalse(B.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ } else {
+ assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ }
+
+ A.close();
+ B.close();
+ }
+
+ private void testThreeClientsForIsLowestPriority(int prioA, int prioB, int prioC) {
+
+ Tuner A = new Tuner(mContext, null, prioA);
+ Tuner B = new Tuner(mContext, null, prioB);
+ Tuner C = new Tuner(mContext, null, prioC);
+
+ // all should return true
+ assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBS));
+
+ // let A & C hold resource
+ assignFeResource(A.getClientId(), FrontendSettings.TYPE_DVBT,
+ true /* expectedResult */, 1 /* expectedHandle */);
+
+ assignFeResource(C.getClientId(), FrontendSettings.TYPE_DVBC,
+ true /* expectedResult */, 5 /* expectedHandle */);
+
+ // should return false for B only if A < B
+ if (prioA > prioB && prioB > prioC) {
+ assertFalse(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertFalse(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+ assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ } else if (prioA > prioC && prioC > prioB) {
+ assertFalse(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+ assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ } else if (prioA > prioC && prioC > prioB) {
+ assertFalse(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+ assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ } else if (prioB > prioA && prioA > prioC) {
+ assertFalse(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertFalse(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+ assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ } else if (prioC > prioA && prioA > prioB) {
+ assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+ assertFalse(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ } else if (prioB > prioC && prioC > prioA) {
+ assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertFalse(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+ assertFalse(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ } else if (prioC > prioB && prioB > prioA) {
+ assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertFalse(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+ assertFalse(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ } else if (prioA == prioB && prioB == prioC) {
+ assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+ assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ } else if (prioA == prioB && prioB > prioC) {
+ assertFalse(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertFalse(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+ assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ } else if (prioA > prioB && prioB == prioC) {
+ assertFalse(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+ assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ } else if (prioA == prioC && prioC > prioB) {
+ assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+ assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ } else if (prioB > prioA && prioA == prioC) {
+ assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertFalse(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+ assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ } else if (prioC > prioA && prioA == prioB) {
+ assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+ assertFalse(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ } else if (prioB == prioC && prioC > prioA) {
+ assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+ assertFalse(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+ assertFalse(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+ }
+
+ A.close();
+ B.close();
+ C.close();
+ }
+
+ @Test
+ public void testSharedFilterOneProcess() throws Exception {
+ Filter f = createTsSectionFilter(mTuner, getExecutor(), getFilterCallback());
+ assertTrue(f != null);
+
+ String token1 = f.acquireSharedFilterToken();
+ assertTrue(token1 != null);
+
+ String token2 = f.acquireSharedFilterToken();
+ assertTrue(token2 == null);
+
+ // Tune a frontend before start the filter
+ List<Integer> ids = mTuner.getFrontendIds();
+ assertFalse(ids.isEmpty());
+
+ FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+ int res = mTuner.tune(createFrontendSettings(info));
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ Settings settings = SectionSettingsWithTableInfo
+ .builder(Filter.TYPE_TS)
+ .setTableId(2)
+ .setVersion(1)
+ .setCrcEnabled(true)
+ .setRaw(false)
+ .setRepeat(false)
+ .build();
+ FilterConfiguration config = TsFilterConfiguration
+ .builder()
+ .setTpid(10)
+ .setSettings(settings)
+ .build();
+
+ assertEquals(f.configure(config), Tuner.RESULT_INVALID_STATE);
+ assertEquals(f.setMonitorEventMask(Filter.MONITOR_EVENT_SCRAMBLING_STATUS),
+ Tuner.RESULT_INVALID_STATE);
+ assertEquals(f.setDataSource(null), Tuner.RESULT_INVALID_STATE);
+ assertEquals(f.start(), Tuner.RESULT_INVALID_STATE);
+ assertEquals(f.flush(), Tuner.RESULT_INVALID_STATE);
+ assertEquals(f.read(new byte[3], 0, 3), 0);
+ assertEquals(f.stop(), Tuner.RESULT_INVALID_STATE);
+
+ res = mTuner.cancelTuning();
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ f.freeSharedFilterToken(token1);
+ f.close();
+ f = null;
+ }
+
+ @Test
+ public void testSharedFilterTwoProcessesCloseInSharedFilter() throws Exception {
+ String token = mSharedFilterTestServer.acquireSharedFilterToken();
+ assertTrue(token != null);
+ SharedFilter f =
+ Tuner.openSharedFilter(mContext, token, getExecutor(), getSharedFilterCallback());
+ assertTrue(f != null);
+
+ assertEquals(f.start(), Tuner.RESULT_SUCCESS);
+ assertEquals(f.flush(), Tuner.RESULT_SUCCESS);
+ assertTrue(f.read(new byte[3], 0, 3) != 0);
+ assertEquals(f.stop(), Tuner.RESULT_SUCCESS);
+
+ mLockLatch = new CountDownLatch(1);
+ f.close();
+ f = null;
+ mSharedFilterTestServer.closeFilter();
+ Thread.sleep(2000);
+ assertEquals(mLockLatch.getCount(), 1);
+ mLockLatch = null;
+ }
+
+ @Test
+ public void testSharedFilterTwoProcessesCloseInFilter() throws Exception {
+ String token = mSharedFilterTestServer.acquireSharedFilterToken();
+ assertTrue(token != null);
+
+ SharedFilter f =
+ Tuner.openSharedFilter(mContext, token, getExecutor(), getSharedFilterCallback());
+ assertTrue(f != null);
+
+ assertEquals(f.start(), Tuner.RESULT_SUCCESS);
+ assertEquals(f.flush(), Tuner.RESULT_SUCCESS);
+ assertTrue(f.read(new byte[3], 0, 3) != 0);
+ assertEquals(f.stop(), Tuner.RESULT_SUCCESS);
+
+ mLockLatch = new CountDownLatch(1);
+ mSharedFilterTestServer.closeFilter();
+ assertTrue(mLockLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ mLockLatch = null;
+ f.close();
+ f = null;
+ }
+
+ @Test
+ public void testSharedFilterTwoProcessesReleaseInFilter() throws Exception {
+ String token = mSharedFilterTestServer.acquireSharedFilterToken();
+ assertTrue(token != null);
+
+ SharedFilter f =
+ Tuner.openSharedFilter(mContext, token, getExecutor(), getSharedFilterCallback());
+ assertTrue(f != null);
+
+ assertEquals(f.start(), Tuner.RESULT_SUCCESS);
+ assertEquals(f.flush(), Tuner.RESULT_SUCCESS);
+ assertTrue(f.read(new byte[3], 0, 3) != 0);
+ assertEquals(f.stop(), Tuner.RESULT_SUCCESS);
+
+ mLockLatch = new CountDownLatch(1);
+ mSharedFilterTestServer.freeSharedFilterToken(token);
+ assertTrue(mLockLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ mLockLatch = null;
+
+ mSharedFilterTestServer.closeFilter();
+ f.close();
+ f = null;
+ }
+
+ @Test
+ public void testSharedFilterTwoProcessesVerifySharedFilter() throws Exception {
+ Filter f = createTsSectionFilter(mTuner, getExecutor(), getFilterCallback());
+ assertTrue(f != null);
+
+ String token = f.acquireSharedFilterToken();
+ assertTrue(token != null);
+
+ // Tune a frontend before start the shared filter
+ List<Integer> ids = mTuner.getFrontendIds();
+ assertFalse(ids.isEmpty());
+
+ FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+ int res = mTuner.tune(createFrontendSettings(info));
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+ assertTrue(mSharedFilterTestServer.verifySharedFilter(token));
+
+ res = mTuner.cancelTuning();
+ assertEquals(Tuner.RESULT_SUCCESS, res);
+
+ f.freeSharedFilterToken(token);
+ f.close();
+ f = null;
+ }
+
+ @Test
+ public void testFilterTimeDelay() throws Exception {
+ Filter f = createTsSectionFilter(mTuner, getExecutor(), getFilterCallback());
+
+ int timeDelayInMs = 5000;
+ Instant start = Instant.now();
+ int status = f.delayCallbackUntilMillisElapsed(timeDelayInMs);
+
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+ // start / stop prevents initial race condition after first setting the time delay.
+ f.start();
+ f.stop();
+
+ mLockLatch = new CountDownLatch(1);
+ f.start();
+ assertTrue(mLockLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ Instant finish = Instant.now();
+ Duration timeElapsed = Duration.between(start, finish);
+ assertTrue(timeElapsed.toMillis() >= timeDelayInMs);
+ } else {
+ assertEquals(Tuner.RESULT_UNAVAILABLE, status);
+ }
+ f.close();
+ f = null;
+ }
+
+ @Test
+ public void testFilterDataSizeDelay() throws Exception {
+ Filter f = createTsSectionFilter(mTuner, getExecutor(), getFilterCallback());
+ int status = f.delayCallbackUntilBytesAccumulated(5000);
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+ assertEquals(Tuner.RESULT_SUCCESS, status);
+ } else {
+ assertEquals(Tuner.RESULT_UNAVAILABLE, status);
+ }
+ f.close();
+ }
+
+ @Test
+ public void testMaxNumberOfFrontends() throws Exception {
+ List<Integer> ids = mTuner.getFrontendIds();
+ assertFalse(ids.isEmpty());
+ for (int i = 0; i < ids.size(); i++) {
+ int type = mTuner.getFrontendInfoById(ids.get(i)).getType();
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_2_0)) {
+ int defaultMax = -1;
+ int status;
+ // Check default value
+ defaultMax = mTuner.getMaxNumberOfFrontends(type);
+ assertTrue(defaultMax > 0);
+ // Set to -1
+ status = mTuner.setMaxNumberOfFrontends(type, -1);
+ assertEquals(Tuner.RESULT_INVALID_ARGUMENT, status);
+ // Set to defaultMax + 1
+ status = mTuner.setMaxNumberOfFrontends(type, defaultMax + 1);
+ assertEquals(Tuner.RESULT_INVALID_ARGUMENT, status);
+ // Set to 0
+ status = mTuner.setMaxNumberOfFrontends(type, 0);
+ assertEquals(Tuner.RESULT_SUCCESS, status);
+ // Check after set
+ int currentMax = -1;
+ currentMax = mTuner.getMaxNumberOfFrontends(type);
+ assertEquals(currentMax, 0);
+ // Reset to default
+ status = mTuner.setMaxNumberOfFrontends(type, defaultMax);
+ assertEquals(Tuner.RESULT_SUCCESS, status);
+ currentMax = mTuner.getMaxNumberOfFrontends(type);
+ assertEquals(defaultMax, currentMax);
+ } else {
+ int defaultMax = mTuner.getMaxNumberOfFrontends(type);
+ assertEquals(defaultMax, -1);
+ int status = mTuner.setMaxNumberOfFrontends(type, 0);
+ assertEquals(Tuner.RESULT_UNAVAILABLE, status);
+ }
+ }
+ }
+
+ public static Filter createTsSectionFilter(
+ Tuner tuner, Executor e, FilterCallback cb) {
+ Filter f = tuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_SECTION, 1000, e, cb);
+ Settings settings = SectionSettingsWithTableInfo
+ .builder(Filter.TYPE_TS)
+ .setTableId(2)
+ .setVersion(1)
+ .setCrcEnabled(true)
+ .setRaw(false)
+ .setRepeat(false)
+ .build();
+ FilterConfiguration config = TsFilterConfiguration
+ .builder()
+ .setTpid(10)
+ .setSettings(settings)
+ .build();
+ f.configure(config);
+ f.setMonitorEventMask(
+ Filter.MONITOR_EVENT_SCRAMBLING_STATUS | Filter.MONITOR_EVENT_IP_CID_CHANGE);
+
+ return f;
}
private boolean hasTuner() {
@@ -838,14 +2362,33 @@
testRestartEvent(filter, (RestartEvent) e);
}
}
+ if (mLockLatch != null) {
+ mLockLatch.countDown();
+ }
}
@Override
public void onFilterStatusChanged(Filter filter, int status) {}
};
}
+ private SharedFilterCallback getSharedFilterCallback() {
+ return new SharedFilterCallback() {
+ @Override
+ public void onFilterEvent(SharedFilter filter, FilterEvent[] events) {}
+ @Override
+ public void onFilterStatusChanged(SharedFilter filter, int status) {
+ if (status == SharedFilter.STATUS_INACCESSIBLE) {
+ if (mLockLatch != null) {
+ mLockLatch.countDown();
+ }
+ }
+ }
+ };
+ }
+
private void testDownloadEvent(Filter filter, DownloadEvent e) {
e.getItemId();
+ e.getDownloadId();
e.getMpuSequenceNumber();
e.getItemFragmentIndex();
e.getLastItemFragmentIndex();
@@ -868,6 +2411,8 @@
e.getStreamId();
e.isPtsPresent();
e.getPts();
+ e.isDtsPresent();
+ e.getDts();
e.getDataLength();
e.getOffset();
e.getLinearBlock();
@@ -876,6 +2421,7 @@
e.getAudioHandle();
e.getMpuSequenceNumber();
e.isPrivateData();
+ e.getScIndexMask();
AudioDescriptor ad = e.getExtraMetaData();
if (ad != null) {
ad.getAdFade();
@@ -917,7 +2463,8 @@
e.getTableId();
e.getVersion();
e.getSectionNumber();
- long length = e.getDataLength();
+ e.getDataLength();
+ long length = e.getDataLengthLong();
if (length > 0) {
byte[] buffer = new byte[(int) length];
assertNotEquals(0, filter.read(buffer, 0, length));
@@ -969,10 +2516,10 @@
};
}
- private FrontendSettings createFrontendSettings(FrontendInfo info) {
+ static public FrontendSettings createFrontendSettings(FrontendInfo info) {
FrontendCapabilities caps = info.getFrontendCapabilities();
- int minFreq = info.getFrequencyRange().getLower();
- int maxFreq = info.getFrequencyRange().getUpper();
+ long minFreq = info.getFrequencyRangeLong().getLower();
+ long maxFreq = info.getFrequencyRangeLong().getUpper();
FrontendCapabilities feCaps = info.getFrontendCapabilities();
switch(info.getType()) {
case FrontendSettings.TYPE_ANALOG: {
@@ -981,7 +2528,7 @@
int sif = getFirstCapable(analogCaps.getSifStandardCapability());
return AnalogFrontendSettings
.builder()
- .setFrequency(minFreq)
+ .setFrequencyLong(55250000) //2nd freq of VHF
.setSignalType(signalType)
.setSifStandard(sif)
.build();
@@ -993,11 +2540,11 @@
Atsc3FrontendSettings settings =
Atsc3FrontendSettings
.builder()
- .setFrequency(minFreq)
+ .setFrequencyLong(473000000) // 1st freq of UHF
.setBandwidth(bandwidth)
.setDemodOutputFormat(demod)
.build();
- settings.setEndFrequency(maxFreq);
+ settings.setEndFrequencyLong(maxFreq);
return settings;
}
case FrontendSettings.TYPE_ATSC: {
@@ -1005,7 +2552,7 @@
int modulation = getFirstCapable(atscCaps.getModulationCapability());
return AtscFrontendSettings
.builder()
- .setFrequency(minFreq)
+ .setFrequencyLong(479000000) // 2nd freq of UHF
.setModulation(modulation)
.build();
}
@@ -1017,12 +2564,12 @@
DvbcFrontendSettings settings =
DvbcFrontendSettings
.builder()
- .setFrequency(minFreq)
+ .setFrequencyLong(490000000)
.setModulation(modulation)
.setInnerFec(fec)
.setAnnex(annex)
.build();
- settings.setEndFrequency(maxFreq);
+ settings.setEndFrequencyLong(maxFreq);
return settings;
}
case FrontendSettings.TYPE_DVBS: {
@@ -1032,11 +2579,11 @@
DvbsFrontendSettings settings =
DvbsFrontendSettings
.builder()
- .setFrequency(minFreq)
+ .setFrequencyLong(950000000) //950Mhz
.setModulation(modulation)
.setStandard(standard)
.build();
- settings.setEndFrequency(maxFreq);
+ settings.setEndFrequencyLong(maxFreq);
return settings;
}
case FrontendSettings.TYPE_DVBT: {
@@ -1047,9 +2594,9 @@
int codeRate = getFirstCapable(dvbtCaps.getCodeRateCapability());
int hierarchy = getFirstCapable(dvbtCaps.getHierarchyCapability());
int guardInterval = getFirstCapable(dvbtCaps.getGuardIntervalCapability());
- return DvbtFrontendSettings
+ DvbtFrontendSettings settings = DvbtFrontendSettings
.builder()
- .setFrequency(minFreq)
+ .setFrequencyLong(498000000)
.setTransmissionMode(transmission)
.setBandwidth(bandwidth)
.setConstellation(constellation)
@@ -1060,28 +2607,34 @@
.setStandard(DvbtFrontendSettings.STANDARD_T)
.setMiso(false)
.build();
+ settings.setEndFrequencyLong(maxFreq);
+ return settings;
}
case FrontendSettings.TYPE_ISDBS3: {
Isdbs3FrontendCapabilities isdbs3Caps = (Isdbs3FrontendCapabilities) caps;
int modulation = getFirstCapable(isdbs3Caps.getModulationCapability());
int codeRate = getFirstCapable(isdbs3Caps.getCodeRateCapability());
- return Isdbs3FrontendSettings
+ Isdbs3FrontendSettings settings = Isdbs3FrontendSettings
.builder()
- .setFrequency(minFreq)
+ .setFrequencyLong(1000000000) //1000 Mhz
.setModulation(modulation)
.setCodeRate(codeRate)
.build();
+ settings.setEndFrequencyLong(maxFreq);
+ return settings;
}
case FrontendSettings.TYPE_ISDBS: {
IsdbsFrontendCapabilities isdbsCaps = (IsdbsFrontendCapabilities) caps;
int modulation = getFirstCapable(isdbsCaps.getModulationCapability());
int codeRate = getFirstCapable(isdbsCaps.getCodeRateCapability());
- return IsdbsFrontendSettings
+ IsdbsFrontendSettings settings = IsdbsFrontendSettings
.builder()
- .setFrequency(minFreq)
+ .setFrequencyLong(1050000000) //1050 Mhz
.setModulation(modulation)
.setCodeRate(codeRate)
.build();
+ settings.setEndFrequencyLong(maxFreq);
+ return settings;
}
case FrontendSettings.TYPE_ISDBT: {
IsdbtFrontendCapabilities isdbtCaps = (IsdbtFrontendCapabilities) caps;
@@ -1090,15 +2643,45 @@
int modulation = getFirstCapable(isdbtCaps.getModulationCapability());
int codeRate = getFirstCapable(isdbtCaps.getCodeRateCapability());
int guardInterval = getFirstCapable(isdbtCaps.getGuardIntervalCapability());
- return IsdbtFrontendSettings
- .builder()
- .setFrequency(minFreq)
- .setModulation(modulation)
- .setBandwidth(bandwidth)
- .setMode(mode)
- .setCodeRate(codeRate)
- .setGuardInterval(guardInterval)
- .build();
+ int timeInterleaveMode =
+ getFirstCapable(isdbtCaps.getTimeInterleaveModeCapability());
+ boolean isSegmentAutoSupported = isdbtCaps.isSegmentAutoSupported();
+ boolean isFullSegmentSupported = isdbtCaps.isFullSegmentSupported();
+
+ IsdbtFrontendSettings.Builder builder = IsdbtFrontendSettings.builder();
+ builder.setFrequencyLong(527143000); //22 ch 527.143 MHz
+ builder.setBandwidth(bandwidth);
+ builder.setMode(mode);
+ builder.setGuardInterval(guardInterval);
+
+ if (!TunerVersionChecker.isHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_2_0)) {
+ builder.setModulation(modulation);
+ builder.setCodeRate(codeRate);
+ } else {
+ IsdbtFrontendSettings.IsdbtLayerSettings.Builder layerBuilder =
+ IsdbtFrontendSettings.IsdbtLayerSettings.builder();
+ layerBuilder.setTimeInterleaveMode(timeInterleaveMode);
+ layerBuilder.setModulation(modulation);
+ layerBuilder.setCodeRate(codeRate);
+ if (isSegmentAutoSupported) {
+ layerBuilder.setNumberOfSegments(0xFF);
+ } else {
+ if (isFullSegmentSupported) {
+ layerBuilder.setNumberOfSegments(13);
+ } else {
+ layerBuilder.setNumberOfSegments(1);
+ }
+ }
+ IsdbtFrontendSettings.IsdbtLayerSettings layer = layerBuilder.build();
+ builder.setLayerSettings(
+ new IsdbtFrontendSettings.IsdbtLayerSettings[] {layer});
+ builder.setPartialReceptionFlag(
+ IsdbtFrontendSettings.PARTIAL_RECEPTION_FLAG_TRUE);
+ }
+ IsdbtFrontendSettings settings = builder.build();
+ settings.setEndFrequencyLong(maxFreq);
+ return settings;
}
case FrontendSettings.TYPE_DTMB: {
DtmbFrontendCapabilities dtmbCaps = (DtmbFrontendCapabilities) caps;
@@ -1113,7 +2696,7 @@
DtmbFrontendSettings settings =
DtmbFrontendSettings
.builder()
- .setFrequency(minFreq)
+ .setFrequencyLong(506000000)
.setModulation(modulation)
.setTransmissionMode(transmissionMode)
.setBandwidth(bandwidth)
@@ -1121,7 +2704,7 @@
.setGuardInterval(guardInterval)
.setTimeInterleaveMode(timeInterleaveMode)
.build();
- settings.setEndFrequency(maxFreq);
+ settings.setEndFrequencyLong(maxFreq);
return settings;
}
default:
@@ -1130,7 +2713,7 @@
return null;
}
- private int getFirstCapable(int caps) {
+ static public int getFirstCapable(int caps) {
if (caps == 0) return 0;
int mask = 1;
while ((mask & caps) == 0) {
@@ -1139,7 +2722,7 @@
return (mask & caps);
}
- private long getFirstCapable(long caps) {
+ static public long getFirstCapable(long caps) {
if (caps == 0) return 0;
long mask = 1;
while ((mask & caps) == 0) {
@@ -1158,6 +2741,13 @@
}
@Override
+ public void onUnlocked() {
+ if (mLockLatch != null) {
+ mLockLatch.countDown();
+ }
+ }
+
+ @Override
public void onScanStopped() {}
@Override
@@ -1167,6 +2757,11 @@
public void onFrequenciesReported(int[] frequency) {}
@Override
+ public void onFrequenciesLongReported(long[] frequencies) {
+ ScanCallback.super.onFrequenciesLongReported(frequencies);
+ }
+
+ @Override
public void onSymbolRatesReported(int[] rate) {}
@Override
@@ -1217,6 +2812,80 @@
public void onDvbcAnnexReported(int dvbcAnnext) {
ScanCallback.super.onDvbcAnnexReported(dvbcAnnext);
}
+
+ @Override
+ public void onDvbtCellIdsReported(int[] dvbtCellIds) {
+ ScanCallback.super.onDvbtCellIdsReported(dvbtCellIds);
+ }
};
}
+
+ // TunerHandler utility for testing Tuner api calls in a different thread
+ private static final int MSG_TUNER_HANDLER_CREATE = 1;
+ private static final int MSG_TUNER_HANDLER_TUNE = 2;
+ private static final int MSG_TUNER_HANDLER_CLOSE = 3;
+
+ private ConditionVariable mTunerHandlerTaskComplete = new ConditionVariable();
+
+ private TunerHandler createTunerHandler(Looper looper) {
+ if (looper != null) {
+ return new TunerHandler(looper);
+ } else if ((looper = Looper.myLooper()) != null) {
+ return new TunerHandler(looper);
+ } else if ((looper = Looper.getMainLooper()) != null) {
+ return new TunerHandler(looper);
+ }
+ return null;
+ }
+
+ private class TunerHandler extends Handler {
+ Object mLock = new Object();
+ Tuner mHandlersTuner;
+ int mResult;
+
+ private TunerHandler(Looper looper) {
+ super(looper);
+ }
+
+ public Tuner getTuner() {
+ synchronized (mLock) {
+ return mHandlersTuner;
+ }
+ }
+
+ public int getResult() {
+ synchronized (mLock) {
+ return mResult;
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_TUNER_HANDLER_CREATE: {
+ synchronized (mLock) {
+ int useCase = msg.arg1;
+ mHandlersTuner = new Tuner(mContext, null, useCase);
+ }
+ break;
+ }
+ case MSG_TUNER_HANDLER_TUNE: {
+ synchronized (mLock) {
+ FrontendSettings feSettings = (FrontendSettings) msg.obj;
+ mResult = mHandlersTuner.tune(feSettings);
+ }
+ break;
+ }
+ case MSG_TUNER_HANDLER_CLOSE: {
+ synchronized (mLock) {
+ mHandlersTuner.close();
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ mTunerHandlerTaskComplete.open();
+ }
+ }
}
diff --git a/tests/tests/uidmigration/Android.bp b/tests/tests/uidmigration/Android.bp
new file mode 100644
index 0000000..dc47946
--- /dev/null
+++ b/tests/tests/uidmigration/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsSharedUserMigrationTestCases",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "compatibility-device-util-axt",
+ "androidx.test.rules",
+ "ctstestrunner-axt",
+ "permission-test-util-lib",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
diff --git a/tests/tests/uidmigration/AndroidManifest.xml b/tests/tests/uidmigration/AndroidManifest.xml
new file mode 100644
index 0000000..c3530da
--- /dev/null
+++ b/tests/tests/uidmigration/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.uidmigration.cts" >
+
+ <application android:label="SharedUserMigrationTest" />
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.uidmigration.cts"
+ android:label="CTS tests of android.uidmigration" >
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
diff --git a/tests/tests/uidmigration/AndroidTest.xml b/tests/tests/uidmigration/AndroidTest.xml
new file mode 100644
index 0000000..5c7f26f
--- /dev/null
+++ b/tests/tests/uidmigration/AndroidTest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 Shared UID Migration test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsSharedUserMigrationInstallTestApp.apk->/data/local/tmp/cts/uidmigration/InstallTestApp.apk" />
+ <option name="push" value="CtsSharedUserMigrationInstallTestAppRm.apk->/data/local/tmp/cts/uidmigration/InstallTestAppRm.apk" />
+ <option name="push" value="CtsSharedUserMigrationInstallTestAppMax.apk->/data/local/tmp/cts/uidmigration/InstallTestAppMax.apk" />
+ <option name="push" value="CtsSharedUserMigrationInstallTestApp2.apk->/data/local/tmp/cts/uidmigration/InstallTestApp2.apk" />
+ <option name="push" value="CtsSharedUserMigrationPermissionTestApp1.apk->/data/local/tmp/cts/uidmigration/PermissionTestApp1.apk" />
+ <option name="push" value="CtsSharedUserMigrationPermissionTestApp2.apk->/data/local/tmp/cts/uidmigration/PermissionTestApp2.apk" />
+ <option name="push" value="CtsSharedUserMigrationPermissionTestApp3.apk->/data/local/tmp/cts/uidmigration/PermissionTestApp3.apk" />
+ <option name="push" value="CtsSharedUserMigrationDataTestApp1.apk->/data/local/tmp/cts/uidmigration/DataTestApp1.apk" />
+ <option name="push" value="CtsSharedUserMigrationDataTestApp2.apk->/data/local/tmp/cts/uidmigration/DataTestApp2.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsSharedUserMigrationTestCases.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.uidmigration.cts" />
+ </test>
+</configuration>
diff --git a/tests/tests/uidmigration/DataTestApp/Android.bp b/tests/tests/uidmigration/DataTestApp/Android.bp
new file mode 100644
index 0000000..2acc29c
--- /dev/null
+++ b/tests/tests/uidmigration/DataTestApp/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsSharedUserMigrationDataTestApp1",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ srcs: ["src/**/*.java"],
+}
+
+android_test_helper_app {
+ name: "CtsSharedUserMigrationDataTestApp2",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ manifest: "AndroidManifest2.xml",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ srcs: ["src/**/*.java"],
+}
diff --git a/tests/tests/uidmigration/DataTestApp/AndroidManifest.xml b/tests/tests/uidmigration/DataTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..9ec1d30
--- /dev/null
+++ b/tests/tests/uidmigration/DataTestApp/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.uidmigration.cts.DataTestApp"
+ android:sharedUserId="android.uidmigration.cts" >
+ <application>
+ <provider
+ android:name="android.uidmigration.cts.DataProvider"
+ android:authorities="android.uidmigration.cts.DataTestApp.provider"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/uidmigration/DataTestApp/AndroidManifest2.xml b/tests/tests/uidmigration/DataTestApp/AndroidManifest2.xml
new file mode 100644
index 0000000..d973efc
--- /dev/null
+++ b/tests/tests/uidmigration/DataTestApp/AndroidManifest2.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.uidmigration.cts.DataTestApp" >
+
+ <queries>
+ <package android:name="android.uidmigration.cts" />
+ </queries>
+
+ <application>
+ <provider
+ android:name="android.uidmigration.cts.DataProvider"
+ android:authorities="android.uidmigration.cts.DataTestApp.provider"
+ android:exported="true" />
+ <receiver
+ android:name="android.uidmigration.cts.UpdateReceiver"
+ android:exported="false" >
+ <intent-filter>
+ <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
diff --git a/tests/tests/uidmigration/DataTestApp/src/android/uidmigration/cts/DataProvider.java b/tests/tests/uidmigration/DataTestApp/src/android/uidmigration/cts/DataProvider.java
new file mode 100644
index 0000000..7ce4cf9
--- /dev/null
+++ b/tests/tests/uidmigration/DataTestApp/src/android/uidmigration/cts/DataProvider.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.uidmigration.cts;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+
+import java.util.UUID;
+
+public class DataProvider extends ContentProvider {
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ Bundle data = new Bundle();
+ SharedPreferences prefs = getContext().getSharedPreferences("prefs", Context.MODE_PRIVATE);
+ String uuid = prefs.getString("uuid", null);
+ if (uuid == null) {
+ uuid = UUID.randomUUID().toString();
+ prefs.edit().putString("uuid", uuid).commit();
+ }
+ data.putString("uuid", uuid);
+ return data;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ 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/tests/uidmigration/DataTestApp/src/android/uidmigration/cts/UpdateReceiver.java b/tests/tests/uidmigration/DataTestApp/src/android/uidmigration/cts/UpdateReceiver.java
new file mode 100644
index 0000000..af529c7
--- /dev/null
+++ b/tests/tests/uidmigration/DataTestApp/src/android/uidmigration/cts/UpdateReceiver.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uidmigration.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Process;
+
+public class UpdateReceiver extends BroadcastReceiver {
+
+ private static final String CTS_TEST_PKG = "android.uidmigration.cts";
+ private static final String ACTION_COUNTDOWN = "android.uidmigration.cts.ACTION_COUNTDOWN";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (!Intent.ACTION_MY_PACKAGE_REPLACED.equals(action)) {
+ return;
+ }
+
+ // Notify the tester app
+ Intent i = new Intent(ACTION_COUNTDOWN);
+ i.setPackage(CTS_TEST_PKG);
+ i.putExtra(Intent.EXTRA_UID, Process.myUid());
+ context.sendBroadcast(i);
+ }
+}
diff --git a/tests/tests/uidmigration/InstallTestApp/Android.bp b/tests/tests/uidmigration/InstallTestApp/Android.bp
new file mode 100644
index 0000000..b0f06bc
--- /dev/null
+++ b/tests/tests/uidmigration/InstallTestApp/Android.bp
@@ -0,0 +1,64 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsSharedUserMigrationInstallTestApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsSharedUserMigrationInstallTestApp2",
+ package_name: "android.uidmigration.cts.InstallTestApp2",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsSharedUserMigrationInstallTestAppRm",
+ defaults: ["cts_defaults"],
+ manifest: "AndroidManifestRm.xml",
+ sdk_version: "current",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsSharedUserMigrationInstallTestAppMax",
+ defaults: ["cts_defaults"],
+ manifest: "AndroidManifestMax.xml",
+ sdk_version: "current",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
diff --git a/tests/tests/uidmigration/InstallTestApp/AndroidManifest.xml b/tests/tests/uidmigration/InstallTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..c49500f
--- /dev/null
+++ b/tests/tests/uidmigration/InstallTestApp/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.uidmigration.cts.InstallTestApp"
+ android:sharedUserId="android.uidmigration.cts" >
+ <application android:hasCode="false"/>
+</manifest>
diff --git a/tests/tests/uidmigration/InstallTestApp/AndroidManifestMax.xml b/tests/tests/uidmigration/InstallTestApp/AndroidManifestMax.xml
new file mode 100644
index 0000000..e657934
--- /dev/null
+++ b/tests/tests/uidmigration/InstallTestApp/AndroidManifestMax.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.uidmigration.cts.InstallTestApp"
+ android:sharedUserId="android.uidmigration.cts"
+ android:sharedUserMaxSdkVersion="30" >
+ <application android:hasCode="false"/>
+</manifest>
diff --git a/tests/tests/uidmigration/InstallTestApp/AndroidManifestRm.xml b/tests/tests/uidmigration/InstallTestApp/AndroidManifestRm.xml
new file mode 100644
index 0000000..5b9eb27
--- /dev/null
+++ b/tests/tests/uidmigration/InstallTestApp/AndroidManifestRm.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.uidmigration.cts.InstallTestApp" >
+ <application android:hasCode="false"/>
+</manifest>
diff --git a/tests/tests/uidmigration/OWNERS b/tests/tests/uidmigration/OWNERS
new file mode 100644
index 0000000..e69fd97
--- /dev/null
+++ b/tests/tests/uidmigration/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 315013
+topjohnwu@google.com
+ashfall@google.com
diff --git a/tests/tests/uidmigration/PermissionTestApp/Android.bp b/tests/tests/uidmigration/PermissionTestApp/Android.bp
new file mode 100644
index 0000000..2bdc777
--- /dev/null
+++ b/tests/tests/uidmigration/PermissionTestApp/Android.bp
@@ -0,0 +1,52 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsSharedUserMigrationPermissionTestApp1",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsSharedUserMigrationPermissionTestApp2",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ manifest: "AndroidManifest2.xml",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsSharedUserMigrationPermissionTestApp3",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ manifest: "AndroidManifest3.xml",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
diff --git a/tests/tests/uidmigration/PermissionTestApp/AndroidManifest.xml b/tests/tests/uidmigration/PermissionTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..5d6297e
--- /dev/null
+++ b/tests/tests/uidmigration/PermissionTestApp/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.uidmigration.cts.PermissionTestApp"
+ android:sharedUserId="android.uidmigration.cts" >
+ <application android:hasCode="false"/>
+</manifest>
diff --git a/tests/tests/uidmigration/PermissionTestApp/AndroidManifest2.xml b/tests/tests/uidmigration/PermissionTestApp/AndroidManifest2.xml
new file mode 100644
index 0000000..ac464c5
--- /dev/null
+++ b/tests/tests/uidmigration/PermissionTestApp/AndroidManifest2.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.uidmigration.cts.PermissionTestApp.secondary"
+ android:sharedUserId="android.uidmigration.cts" >
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <application android:hasCode="false"/>
+</manifest>
diff --git a/tests/tests/uidmigration/PermissionTestApp/AndroidManifest3.xml b/tests/tests/uidmigration/PermissionTestApp/AndroidManifest3.xml
new file mode 100644
index 0000000..791b354
--- /dev/null
+++ b/tests/tests/uidmigration/PermissionTestApp/AndroidManifest3.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.uidmigration.cts.PermissionTestApp.secondary" >
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <application android:hasCode="false"/>
+</manifest>
diff --git a/tests/tests/uidmigration/TEST_MAPPING b/tests/tests/uidmigration/TEST_MAPPING
new file mode 100644
index 0000000..d63d02c
--- /dev/null
+++ b/tests/tests/uidmigration/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsSharedUserMigrationTestCases",
+ "options": [
+ {
+ "include-filter": "android.uidmigration.cts"
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/tests/uidmigration/src/android/uidmigration/cts/SharedUserMigrationTest.java b/tests/tests/uidmigration/src/android/uidmigration/cts/SharedUserMigrationTest.java
new file mode 100644
index 0000000..3be6eef
--- /dev/null
+++ b/tests/tests/uidmigration/src/android/uidmigration/cts/SharedUserMigrationTest.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.uidmigration.cts;
+
+import static android.Manifest.permission.INTERNET;
+import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+import static android.permission.cts.PermissionUtils.grantPermission;
+import static android.permission.cts.PermissionUtils.isPermissionGranted;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+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.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.content.pm.PackageManager.PackageInfoFlags;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class SharedUserMigrationTest {
+
+ private static final String TMP_APK_PATH = "/data/local/tmp/cts/uidmigration";
+ private static final String INSTALL_TEST_PKG = "android.uidmigration.cts.InstallTestApp";
+ private static final String PERM_TEST_PKG = "android.uidmigration.cts.PermissionTestApp";
+ private static final String DATA_TEST_PKG = "android.uidmigration.cts.DataTestApp";
+ private static final String ACTION_COUNTDOWN = "android.uidmigration.cts.ACTION_COUNTDOWN";
+
+ private static final Throwable NOT_AN_ERROR = new Throwable();
+
+ private Context mContext;
+ private PackageManager mPm;
+
+ @Before
+ public void setup() {
+ mContext = ApplicationProvider.getApplicationContext();
+ mPm = mContext.getPackageManager();
+ }
+
+ @After
+ public void tearDown() {
+ uninstallPackage(INSTALL_TEST_PKG);
+ uninstallPackage(INSTALL_TEST_PKG + "2");
+ uninstallPackage(PERM_TEST_PKG);
+ uninstallPackage(PERM_TEST_PKG + ".secondary");
+ uninstallPackage(DATA_TEST_PKG);
+ }
+
+ @Test
+ public void testAppInstall() throws PackageManager.NameNotFoundException {
+ String apk = TMP_APK_PATH + "/InstallTestApp";
+ assertTrue(installPackage(apk + ".apk"));
+ assertTrue(installPackage(apk + "2.apk"));
+
+ // Both app should share the same UID
+ int uid = mPm.getPackageUid(INSTALL_TEST_PKG, PackageInfoFlags.of(0));
+ String[] pkgs = mPm.getPackagesForUid(uid);
+ assertNotNull(pkgs);
+ assertEquals(2, pkgs.length);
+
+ // Leave shared UID by removing sharedUserId
+ assertTrue(installPackage(apk + "Rm.apk"));
+ pkgs = mPm.getPackagesForUid(uid);
+ assertNotNull(pkgs);
+ assertEquals(1, pkgs.length);
+
+ // Uninstall and reinstall the old app
+ uninstallPackage(INSTALL_TEST_PKG);
+ assertTrue(installPackage(apk + ".apk"));
+ pkgs = mPm.getPackagesForUid(uid);
+ assertNotNull(pkgs);
+ assertEquals(2, pkgs.length);
+
+ // Leave shared UID with sharedUserMaxSdkVersion
+ assertTrue(installPackage(apk + "Max.apk"));
+ pkgs = mPm.getPackagesForUid(uid);
+ assertNotNull(pkgs);
+ assertEquals(1, pkgs.length);
+
+ uninstallPackage(INSTALL_TEST_PKG);
+ uninstallPackage(INSTALL_TEST_PKG + "2");
+ }
+
+ @Test
+ public void testPermissionMigration() throws Exception {
+ String apk = TMP_APK_PATH + "/PermissionTestApp";
+ assertTrue(installPackage(apk + "1.apk"));
+ assertTrue(installPackage(apk + "2.apk"));
+
+ String secondaryPkg = PERM_TEST_PKG + ".secondary";
+
+ // Runtime permissions are not granted by default
+ assertFalse(isPermissionGranted(secondaryPkg, WRITE_EXTERNAL_STORAGE));
+
+ // Grant a runtime permission
+ grantPermission(secondaryPkg, WRITE_EXTERNAL_STORAGE);
+
+ // All apps in the UID group should have the same permissions
+ assertTrue(isPermissionGranted(PERM_TEST_PKG, INTERNET));
+ assertTrue(isPermissionGranted(PERM_TEST_PKG, WRITE_EXTERNAL_STORAGE));
+ assertTrue(isPermissionGranted(secondaryPkg, INTERNET));
+ assertTrue(isPermissionGranted(secondaryPkg, WRITE_EXTERNAL_STORAGE));
+
+ // Upgrade and leave shared UID
+ assertTrue(installPackage(apk + "3.apk"));
+
+ // The app in the original UID group should no longer have the permissions
+ assertFalse(isPermissionGranted(PERM_TEST_PKG, INTERNET));
+ assertFalse(isPermissionGranted(PERM_TEST_PKG, WRITE_EXTERNAL_STORAGE));
+
+ // The upgraded app should still have the permissions
+ assertTrue(isPermissionGranted(secondaryPkg, INTERNET));
+ assertTrue(isPermissionGranted(secondaryPkg, WRITE_EXTERNAL_STORAGE));
+
+ uninstallPackage(PERM_TEST_PKG);
+ uninstallPackage(secondaryPkg);
+ }
+
+ @Test
+ public void testDataMigration() throws PackageManager.NameNotFoundException {
+ String apk = TMP_APK_PATH + "/DataTestApp";
+ assertTrue(installPackage(apk + "1.apk"));
+ int oldUid = mPm.getPackageUid(DATA_TEST_PKG, PackageInfoFlags.of(0));
+
+ String authority = DATA_TEST_PKG + ".provider";
+ ContentResolver resolver = mContext.getContentResolver();
+
+ // Ask the app to generate a new random UUID and persist in data
+ Bundle result = resolver.call(authority, "", null, null);
+ assertNotNull(result);
+ String oldUUID = result.getString("uuid");
+ assertNotNull(oldUUID);
+
+ // Need 2 receivers because the intent filters are not compatible
+ PackageBroadcastVerifier verifier = new PackageBroadcastVerifier(oldUid);
+ BroadcastReceiver r1 = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ verifier.verify(intent);
+ }
+ };
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addDataScheme("package");
+ mContext.registerReceiver(r1, filter);
+
+ BroadcastReceiver r2 = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ verifier.verify(intent);
+ }
+ };
+ filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_UID_REMOVED);
+ filter.addAction(ACTION_COUNTDOWN);
+ mContext.registerReceiver(r2, filter);
+
+ // Update the data test APK and make sure UID changed
+ assertTrue(installPackage(apk + "2.apk"));
+ int newUid = mPm.getPackageUid(DATA_TEST_PKG, PackageInfoFlags.of(0));
+ assertNotEquals(oldUid, newUid);
+
+ // Ensure system broadcasts are delivered properly
+ try {
+ final Throwable e = verifier.poll(5, TimeUnit.SECONDS);
+ if (e != NOT_AN_ERROR) {
+ throw new AssertionError(e);
+ }
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+ assertEquals(newUid, verifier.newUid);
+
+ mContext.unregisterReceiver(r1);
+ mContext.unregisterReceiver(r2);
+
+ // Ask the app again for a UUID. If data migration is working, it shall be the same
+ result = resolver.call(authority, "", null, null);
+ assertNotNull(result);
+ String newUUID = result.getString("uuid");
+ assertEquals(oldUUID, newUUID);
+
+ uninstallPackage(DATA_TEST_PKG);
+ }
+
+ private boolean installPackage(String apkPath) {
+ return runShellCommand("pm install --force-queryable -t " + apkPath).equals("Success\n");
+ }
+
+ private void uninstallPackage(String packageName) {
+ runShellCommand("pm uninstall " + packageName);
+ }
+
+ static class PackageBroadcastVerifier extends ArrayBlockingQueue<Throwable> {
+
+ public int newUid = -1;
+
+ private final int mPreviousUid;
+ private int mCounter = 0;
+
+ PackageBroadcastVerifier(int uid) {
+ super(1);
+ mPreviousUid = uid;
+ }
+
+ void verify(Intent intent) {
+ try {
+ verifyInternal(intent);
+ } catch (Throwable e) {
+ offer(e);
+ }
+ }
+
+ private void verifyInternal(Intent intent) {
+ String action = intent.getAction();
+ assertNotNull(action);
+
+ if (action.equals(Intent.ACTION_UID_REMOVED)) {
+ // Not the test package, none of our business
+ if (intent.getIntExtra(Intent.EXTRA_UID, -1) != mPreviousUid) {
+ return;
+ }
+ }
+
+ Uri data = intent.getData();
+ if (data != null) {
+ assertEquals("package", data.getScheme());
+ String pkg = data.getSchemeSpecificPart();
+ assertNotNull(pkg);
+ // Not the test package, none of our business
+ if (!DATA_TEST_PKG.equals(pkg)) {
+ return;
+ }
+ }
+
+ // Broadcasts must come in the following order:
+ // ACTION_PACKAGE_REMOVED -> ACTION_UID_REMOVED
+ // -> ACTION_PACKAGE_ADDED -> ACTION_COUNTDOWN
+
+ mCounter++;
+
+ switch (action) {
+ case Intent.ACTION_PACKAGE_REMOVED:
+ assertEquals(1, mCounter);
+ assertFalse(intent.hasExtra(Intent.EXTRA_REPLACING));
+ assertTrue(intent.getBooleanExtra(Intent.EXTRA_UID_CHANGING, false));
+ assertEquals(mPreviousUid, intent.getIntExtra(Intent.EXTRA_UID, -1));
+ break;
+ case Intent.ACTION_UID_REMOVED:
+ assertEquals(2, mCounter);
+ assertFalse(intent.hasExtra(Intent.EXTRA_REPLACING));
+ assertTrue(intent.getBooleanExtra(Intent.EXTRA_UID_CHANGING, false));
+ assertEquals(DATA_TEST_PKG, intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
+ break;
+ case Intent.ACTION_PACKAGE_ADDED:
+ assertEquals(3, mCounter);
+ assertFalse(intent.hasExtra(Intent.EXTRA_REPLACING));
+ assertTrue(intent.getBooleanExtra(Intent.EXTRA_UID_CHANGING, false));
+ newUid = intent.getIntExtra(Intent.EXTRA_UID, mPreviousUid);
+ assertNotEquals(mPreviousUid, newUid);
+ assertEquals(mPreviousUid, intent.getIntExtra(Intent.EXTRA_PREVIOUS_UID, -1));
+ break;
+ case ACTION_COUNTDOWN:
+ assertEquals(4, mCounter);
+ assertEquals(newUid, intent.getIntExtra(Intent.EXTRA_UID, -2));
+ // End of actions
+ offer(NOT_AN_ERROR);
+ break;
+ case Intent.ACTION_PACKAGE_REPLACED:
+ fail("PACKAGE_REPLACED should not be called!");
+ break;
+ default:
+ fail("Unknown action received");
+ break;
+ }
+ }
+ }
+}
diff --git a/tests/tests/uirendering/res/layout/draw_order.xml b/tests/tests/uirendering/res/layout/draw_order.xml
new file mode 100644
index 0000000..0ca1efc
--- /dev/null
+++ b/tests/tests/uirendering/res/layout/draw_order.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/test_width"
+ android:layout_height="@dimen/test_height">
+ <View
+ android:id="@+id/blueview"
+ android:layout_width="@dimen/test_width"
+ android:layout_height="@dimen/test_height"
+ android:background="#0000FF" />
+ <View
+ android:id="@+id/greenview"
+ android:layout_width="@dimen/test_width"
+ android:layout_height="@dimen/test_height"
+ android:background="#00FF00" />
+</FrameLayout>
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/AntiAliasingVerifier.java b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/AntiAliasingVerifier.java
new file mode 100644
index 0000000..c05a3ce
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/AntiAliasingVerifier.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bitmapverifiers;
+
+import android.graphics.Rect;
+import android.uirendering.cts.util.CompareUtils;
+
+/**
+ * Tests to see if there is rectangle of a given color, anti-aliased. It checks this by
+ * verifying that a nonzero number of pixels in the area lie strictly between the inner
+ * and outer colors (eg, they are a blend of the two). To verify that a rectangle is
+ * correctly rendered overall, create a RegionVerifier with one Rect to cover the inner
+ * region and another to cover the border region.
+ *
+ * Note that AA is tested by matching the final color against a blend of the inner/outer
+ * colors. To ensure correctness in this test, callers should simplify the test to include
+ * simple colors, eg, Black/Blue and no use of White (which includes colors in all channels).
+ */
+public class AntiAliasingVerifier extends PerPixelBitmapVerifier {
+ private int mOuterColor;
+ private int mInnerColor;
+ private Rect mBorderRect;
+ private int mVerifiedAAPixels = 0;
+
+ public AntiAliasingVerifier(int outerColor, int innerColor, Rect borderRect) {
+ // Zero tolerance since we use failures as signal to test for AA pixels
+ this(outerColor, innerColor, borderRect, 0);
+ }
+
+ public AntiAliasingVerifier(int outerColor, int innerColor, Rect borderRect, int tolerance) {
+ super(tolerance);
+ mOuterColor = outerColor;
+ mInnerColor = innerColor;
+ mBorderRect = borderRect;
+ }
+
+ @Override
+ protected int getExpectedColor(int x, int y) {
+ return mBorderRect.contains(x, y) ? mInnerColor : mOuterColor;
+ }
+
+ @Override
+ public boolean verify(int[] bitmap, int offset, int stride, int width, int height) {
+ boolean result = super.verify(bitmap, offset, stride, width, height);
+ // At a minimum, all but maybe the two end pixels on the left should be AA
+ result &= (mVerifiedAAPixels > (mBorderRect.height() - 2));
+ return result;
+ }
+
+ protected boolean verifyPixel(int x, int y, int observedColor) {
+ boolean result = super.verifyPixel(x, y, observedColor);
+ if (!result) {
+ result = CompareUtils.verifyPixelBetweenColors(observedColor, mOuterColor, mInnerColor);
+ if (result) {
+ ++mVerifiedAAPixels;
+ }
+ }
+ return result;
+ }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
index b007698..84f0803 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
@@ -32,6 +32,8 @@
import android.uirendering.cts.bitmapverifiers.GoldenImageVerifier
import android.uirendering.cts.bitmapverifiers.RectVerifier
import android.uirendering.cts.bitmapverifiers.RegionVerifier
+import android.uirendering.cts.differencevisualizers.PassFailVisualizer
+import android.uirendering.cts.util.BitmapDumper
import junitparams.JUnitParamsRunner
import junitparams.Parameters
import org.junit.Test
@@ -53,6 +55,8 @@
private val ANDROID_IMAGE_DECODER_FINISHED = -10
private val ANDROID_IMAGE_DECODER_INVALID_STATE = -11
+ private val DEBUG_CAPTURE_IMAGES = false
+
private fun getAssets(): AssetManager {
return InstrumentationRegistry.getTargetContext().getAssets()
}
@@ -144,6 +148,7 @@
* @param mssimThreshold The minimum MSSIM value to accept as similar. Some
* images do not match exactly, but they've been
* manually verified to look the same.
+ * @param testName Optional name of the calling test for BitmapDumper.
*/
private fun decodeAndCropFrames(
image: String,
@@ -151,7 +156,8 @@
numFrames: Int,
scaleFactor: Float,
crop: Crop,
- mssimThreshold: Double
+ mssimThreshold: Double,
+ testName: String = ""
) {
val decodeAndCropper = DecodeAndCropper(image, scaleFactor, crop)
var expectedBm = decodeAndCropper.bitmap
@@ -176,7 +182,13 @@
while (true) {
nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
val verifier = GoldenImageVerifier(expectedBm, MSSIMComparer(mssimThreshold))
- assertTrue(verifier.verify(testBm), "$image has mismatch in frame $i")
+ if (!verifier.verify(testBm)) {
+ if (DEBUG_CAPTURE_IMAGES) {
+ BitmapDumper.dumpBitmaps(expectedBm, testBm, "$testName(${image}_$i)",
+ "AImageDecoderTest", PassFailVisualizer());
+ }
+ fail("$image has mismatch in frame $i")
+ }
expectedBm.recycle()
i++
@@ -216,7 +228,11 @@
@Test
@Parameters(method = "animationsAndFrames")
fun testDecodeFramesScaleDown(image: String, frameName: String, numFrames: Int) {
- decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.None, .749)
+ // Perceptually, this image looks reasonable, but the MSSIM is low enough to be
+ // meaningless. It has been manually verified.
+ if (image == "alphabetAnim.gif") return
+ decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.None, .749,
+ "testDecodeFramesScaleDown")
}
@Test
@@ -240,7 +256,11 @@
@Test
@Parameters(method = "animationsAndFrames")
fun testDecodeFramesAndCropTopScaleDown(image: String, frameName: String, numFrames: Int) {
- decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.Top, .749)
+ // Perceptually, this image looks reasonable, but the MSSIM is low enough to be
+ // meaningless. It has been manually verified.
+ if (image == "alphabetAnim.gif") return
+ decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.Top, .749,
+ "testDecodeFramesAndCropTopScaleDown")
}
@Test
@@ -264,7 +284,11 @@
@Test
@Parameters(method = "animationsAndFrames")
fun testDecodeFramesAndCropLeftScaleDown(image: String, frameName: String, numFrames: Int) {
- decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.Left, .596)
+ // Perceptually, this image looks reasonable, but the MSSIM is low enough to be
+ // meaningless. It has been manually verified.
+ if (image == "alphabetAnim.gif") return
+ decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.Left, .596,
+ "testDecodeFramesAndCropLeftScaleDown")
}
@Test
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapTests.java
index 907c88a..912004a 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapTests.java
@@ -28,6 +28,7 @@
import android.graphics.Picture;
import android.graphics.Rect;
import android.os.Handler;
+import android.os.Looper;
import android.uirendering.cts.R;
import android.uirendering.cts.bitmapcomparers.MSSIMComparer;
import android.uirendering.cts.bitmapverifiers.BitmapVerifier;
@@ -49,6 +50,7 @@
import org.junit.runner.RunWith;
import java.util.Arrays;
+import java.util.function.Function;
@LargeTest
@RunWith(AndroidJUnit4.class)
@@ -88,6 +90,7 @@
public void testChangeDuringRtAnimation() {
class RtOnlyFrameCounter implements Window.OnFrameMetricsAvailableListener {
private int count = 0;
+ Function<Integer, Void> onCountChanged = null;
@Override
public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,
@@ -96,6 +99,9 @@
&& frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION) == 0
&& frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION) == 0) {
count++;
+ if (onCountChanged != null) {
+ onCountChanged.apply(count);
+ }
};
}
@@ -121,19 +127,15 @@
mAnimator.setDuration(3000);
mAnimator.start();
- Handler handler = new Handler();
- handler.postDelayed(new Runnable() {
- @Override
- public void run() {
+ Handler handler = new Handler(Looper.getMainLooper());
+ counter.onCountChanged = (Integer count) -> {
+ // Ensure we've produced a couple frames and should now be in the RenderThread
+ // animation. So bitmap changes shouldn't be observed.
+ if (count >= 2) {
child.setColor(Color.RED);
- try {
- Thread.sleep(1000);
- } catch (Exception e) {
- // do nothing
- }
- child.setColor(Color.BLUE);
}
- }, 1000);
+ return null;
+ };
getActivity().getWindow().addOnFrameMetricsAvailableListener(counter, handler);
}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/CanvasTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/CanvasTests.java
index de5da22..e9e88c9 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/CanvasTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/CanvasTests.java
@@ -32,6 +32,7 @@
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.Region;
import android.graphics.Shader;
import android.uirendering.cts.R;
import android.uirendering.cts.bitmapverifiers.BitmapVerifier;
@@ -837,6 +838,60 @@
}
@Test
+ public void testClipIntersectAndDifference() {
+ Point[] testPoints = {
+ new Point(1, 1),
+ new Point(10, 10),
+ new Point(25, 25)
+ };
+ int[] colors = {
+ Color.WHITE,
+ Color.RED,
+ Color.GREEN
+ };
+ createTest()
+ .addCanvasClient((canvas, width, height) -> {
+ // Base is white
+ Paint paint = new Paint();
+ paint.setColor(Color.WHITE);
+ canvas.drawRect(0, 0, width, height, paint);
+
+ // Fill inset with green, which will later be overwitten with
+ // red except for a subsequent difference clip op
+ canvas.clipRect(5, 5, width - 5, height - 5);
+ paint.setColor(Color.GREEN);
+ canvas.drawRect(0, 0, width, height, paint);
+
+ // Cut out the inner region, so that it remains green
+ canvas.clipOutRect(20, 20, width - 20, height - 20);
+ paint.setColor(Color.RED);
+ canvas.drawRect(0, 0, width, height, paint);
+ })
+ .runWithVerifier(new SamplePointVerifier(testPoints, colors));
+ }
+
+ // Expanding region ops (replace, reverse diff, union, and xor) are not allowed for clipping
+ @Test(expected = IllegalArgumentException.class)
+ public void testClipReplace() {
+ new Canvas(getMutableBitmap()).clipRect(0, 0, 10, 10, Region.Op.REPLACE);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testClipReverseDifference() {
+ new Canvas(getMutableBitmap()).clipRect(0, 0, 10, 10, Region.Op.REVERSE_DIFFERENCE);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testClipUnion() {
+ new Canvas(getMutableBitmap()).clipRect(0, 0, 10, 10, Region.Op.UNION);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testClipXor() {
+ new Canvas(getMutableBitmap()).clipRect(0, 0, 10, 10, Region.Op.XOR);
+ }
+
+ @Test
public void testAntiAliasClipping() {
createTest()
.addCanvasClient((canvas, width, height) -> {
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/DrawingOrderTest.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/DrawingOrderTest.java
new file mode 100644
index 0000000..97d6a67
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/DrawingOrderTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Color;
+import android.uirendering.cts.R;
+import android.uirendering.cts.bitmapverifiers.ColorVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+import android.uirendering.cts.testinfrastructure.ViewInitializer;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DrawingOrderTest extends ActivityTestBase {
+
+ @Test
+ public void testDefaultDrawOrder() {
+ createTest().addLayout(R.layout.draw_order, null)
+ .runWithVerifier(new ColorVerifier(Color.GREEN));
+ }
+
+ @Test
+ public void testTranslationZOrder() {
+ createTest().addLayout(R.layout.draw_order, (ViewInitializer) view -> {
+ view.findViewById(R.id.blueview).setTranslationZ(4);
+ view.findViewById(R.id.greenview).setTranslationZ(0);
+ }).runWithVerifier(new ColorVerifier(Color.BLUE));
+ }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/Nulls.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/Nulls.java
new file mode 100644
index 0000000..11e8b56
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/Nulls.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/** Indirection for the Java flow analysis to allow passing `null` for @Nonnull parameters. */
+public class Nulls {
+ @SuppressWarnings("null")
+ public static <T> T type() {
+ return null;
+ }
+
+ @SuppressWarnings("null")
+ public static <T> T[] array() {
+ return null;
+ }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
index bed338c..ae79f4c 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
@@ -36,6 +36,7 @@
import android.graphics.Rect;
import android.graphics.RenderEffect;
import android.graphics.RenderNode;
+import android.graphics.RuntimeShader;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.uirendering.cts.R;
@@ -872,6 +873,42 @@
);
}
+ private static String sRedBlueInversionShader = ""
+ + "uniform shader inputShader;"
+ + "vec4 main(vec2 coord) { "
+ + " vec4 color = inputShader.eval(coord);"
+ + " return vec4(color.b, color.g, color.r, color.a);"
+ + "}";
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testRuntimeShaderRenderEffectInvalidUinformName() {
+ RuntimeShader shader = new RuntimeShader(sRedBlueInversionShader);
+ RenderEffect runtimeEffect = RenderEffect.createRuntimeShaderEffect(
+ shader, "invalidUniformName");
+ }
+
+ @Test
+ public void testRuntimeShaderRenderEffect() {
+ RuntimeShader shader = new RuntimeShader(sRedBlueInversionShader);
+ RenderEffect runtimeEffect = RenderEffect.createRuntimeShaderEffect(
+ shader, "inputShader");
+ final RenderNode renderNode = new RenderNode(null);
+ renderNode.setRenderEffect(runtimeEffect);
+ renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+ {
+ Canvas recordingCanvas = renderNode.beginRecording();
+ Paint paint = new Paint();
+ paint.setColor(Color.BLUE);
+ recordingCanvas.drawRect(0, 0, TEST_WIDTH, TEST_HEIGHT, paint);
+ renderNode.endRecording();
+ }
+
+ createTest()
+ .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+ canvas.drawRenderNode(renderNode);
+ }, true)
+ .runWithVerifier(new ColorVerifier(Color.RED));
+ }
@Test
public void testBlurShaderLargeRadiiEdgeReplication() {
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RuntimeShaderTests.kt b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RuntimeShaderTests.kt
new file mode 100644
index 0000000..0358364
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RuntimeShaderTests.kt
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.BitmapShader
+import android.graphics.BlendMode
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorSpace
+import android.graphics.ComposeShader
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.Picture
+import android.graphics.Rect
+import android.graphics.RuntimeShader
+import android.graphics.Shader
+import android.uirendering.cts.bitmapverifiers.RectVerifier
+import android.uirendering.cts.testinfrastructure.ActivityTestBase
+import android.uirendering.cts.testinfrastructure.CanvasClient
+import android.util.Half
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class RuntimeShaderTests : ActivityTestBase() {
+
+ @Test(expected = NullPointerException::class)
+ fun createWithNullInput() {
+ RuntimeShader(Nulls.type<String>())
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun createWithEmptyInput() {
+ RuntimeShader("")
+ }
+
+ val bitmapShader = BitmapShader(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888),
+ Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+
+ @Test(expected = NullPointerException::class)
+ fun setNullUniformName() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setFloatUniform(Nulls.type<String>(), 0.0f)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun setEmptyUniformName() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setFloatUniform("", 0.0f)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun setInvalidUniformName() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setFloatUniform("invalid", 0.0f)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun setInvalidUniformType() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setFloatUniform("inputInt", 1.0f)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun setInvalidUniformLength() {
+ val shader = RuntimeShader(simpleColorShader)
+ shader.setFloatUniform("inputNonColor", 1.0f, 1.0f, 1.0f)
+ }
+
+ @Test(expected = NullPointerException::class)
+ fun setNullIntUniformName() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setIntUniform(Nulls.type<String>(), 0)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun setEmptyIntUniformName() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setIntUniform("", 0)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun setInvalidIntUniformName() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setIntUniform("invalid", 0)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun setInvalidIntUniformType() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setIntUniform("inputFloat", 1)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun setInvalidIntUniformLength() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setIntUniform("inputInt", 1, 2)
+ }
+
+ @Test(expected = NullPointerException::class)
+ fun setNullColorName() {
+ val shader = RuntimeShader(simpleColorShader)
+ shader.setColorUniform(Nulls.type<String>(), 0)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun setEmptyColorName() {
+ val shader = RuntimeShader(simpleColorShader)
+ shader.setColorUniform("", 0)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun setInvalidColorName() {
+ val shader = RuntimeShader(simpleColorShader)
+ shader.setColorUniform("invalid", 0)
+ }
+
+ @Test(expected = NullPointerException::class)
+ fun setNullColorValue() {
+ val shader = RuntimeShader(simpleColorShader)
+ shader.setColorUniform("inputColor", Nulls.type<Color>())
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun setColorValueNonColorUniform() {
+ val shader = RuntimeShader(simpleColorShader)
+ shader.setColorUniform("inputNonColor", Color.BLUE)
+ }
+
+ @Test(expected = NullPointerException::class)
+ fun setNullShaderName() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setInputShader(Nulls.type<String>(), bitmapShader)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun setEmptyShaderName() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setInputShader("", bitmapShader)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun setInvalidShaderName() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setInputShader("invalid", bitmapShader)
+ }
+
+ @Test(expected = NullPointerException::class)
+ fun setNullShaderValue() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setInputShader("inputShader", Nulls.type<Shader>())
+ }
+
+ @Test(expected = NullPointerException::class)
+ fun setNullBufferName() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setInputBuffer(Nulls.type<String>(), bitmapShader)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun setEmptyBufferName() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setInputBuffer("", bitmapShader)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun setInvalidBufferName() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setInputBuffer("invalid", bitmapShader)
+ }
+
+ @Test(expected = NullPointerException::class)
+ fun setNullBufferValue() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setInputBuffer("inputShader", Nulls.type<BitmapShader>())
+ }
+
+ @Test
+ fun testDefaultUniform() {
+ val shader = RuntimeShader(simpleShader)
+ shader.setInputShader("inputShader", RuntimeShader(simpleRedShader))
+
+ val paint = Paint()
+ paint.shader = shader
+
+ val rect = Rect(10, 10, 80, 80)
+
+ createTest().addCanvasClient(CanvasClient
+ { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+ true).runWithVerifier(RectVerifier(Color.WHITE, Color.BLACK, rect))
+ }
+
+ @Test
+ fun testDefaultColorUniform() {
+ val shader = RuntimeShader(simpleColorShader, true)
+
+ val paint = Paint()
+ paint.shader = shader
+
+ val rect = Rect(10, 10, 80, 80)
+
+ createTest().addCanvasClient(CanvasClient
+ { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+ true).runWithVerifier(RectVerifier(Color.WHITE, Color.BLACK, rect))
+ }
+
+ @Test
+ fun testDefaultInputShader() {
+ val paint = Paint()
+ paint.color = Color.BLUE
+ paint.shader = RuntimeShader(mBlackIfInputNotOpaqueShader)
+
+ val rect = Rect(10, 10, 80, 80)
+
+ createTest().addCanvasClient(CanvasClient
+ { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+ true).runWithVerifier(RectVerifier(Color.WHITE, paint.color, rect))
+ }
+
+ @Test
+ fun testDefaultInputShaderWithPaintAlpha() {
+ val paint = Paint()
+ paint.color = Color.argb(0.5f, 0.0f, 0.0f, 1.0f)
+ paint.shader = RuntimeShader(mBlackIfInputNotOpaqueShader)
+ paint.blendMode = BlendMode.SRC
+
+ val rect = Rect(10, 10, 80, 80)
+
+ // The shader should be evaluated with an opaque paint color and the paint's alpha will be
+ // applied after the shader returns but before it is blended into the destination
+ createTest().addCanvasClient(CanvasClient
+ { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+ true).runWithVerifier(RectVerifier(Color.WHITE, paint.color, rect))
+ }
+
+ @Test
+ fun testInputShaderWithPaintAlpha() {
+ val shader = RuntimeShader(mBlackIfInputNotOpaqueShader)
+ shader.setInputShader("inputShader", RuntimeShader(mSemiTransparentBlueShader))
+
+ val paint = Paint()
+ paint.color = Color.argb(0.5f, 0.0f, 1.0f, .0f)
+ paint.shader = shader
+ paint.blendMode = BlendMode.SRC
+
+ val rect = Rect(10, 10, 80, 80)
+
+ // The shader should be evaluated first then the paint's alpha will be applied after the
+ // shader returns but before it is blended into the destination
+ createTest().addCanvasClient(CanvasClient
+ { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+ true).runWithVerifier(RectVerifier(Color.WHITE, Color.BLACK, rect))
+ }
+
+ @Test
+ fun testInputShaderWithFiltering() {
+ val bitmap = Bitmap.createBitmap(2, 2, Bitmap.Config.ARGB_8888)
+ bitmap.setPixel(0, 0, Color.RED)
+ bitmap.setPixel(1, 0, Color.BLUE)
+ bitmap.setPixel(0, 1, Color.BLUE)
+ bitmap.setPixel(1, 1, Color.RED)
+
+ val bitmapShader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+
+ // use slightly left than half to avoid any confusion on which pixel
+ // is sampled with FILTER_MODE_NEAREST
+ val matrix = Matrix()
+ matrix.postScale(0.49f, 0.49f)
+ bitmapShader.setLocalMatrix(matrix)
+
+ val shader = RuntimeShader(samplingShader)
+ shader.setInputShader("inputShader", bitmapShader)
+
+ val rect = Rect(0, 0, 1, 1)
+ val paint = Paint()
+
+ // The bitmap shader should be sampled with FILTER_MODE_NEAREST as the paint's filtering
+ // flag is not respected
+ paint.shader = shader
+ paint.blendMode = BlendMode.SRC
+ createTest().addCanvasClient(CanvasClient
+ { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+ true).runWithVerifier(RectVerifier(Color.WHITE, Color.RED, rect))
+
+ // The bitmap shader should be sampled with FILTER_MODE_LINEAR as the paint's filtering
+ // flag is not respected
+ paint.isFilterBitmap = false
+ bitmapShader.filterMode = BitmapShader.FILTER_MODE_LINEAR
+ shader.setInputShader("inputShader", bitmapShader)
+ createTest().addCanvasClient(CanvasClient
+ { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+ true).runWithVerifier(RectVerifier(Color.WHITE,
+ Color.valueOf(0.5f, 0.0f, 0.5f).toArgb(), rect))
+ }
+
+ @Test
+ fun testInputBuffer() {
+ val unpremulColor = Color.valueOf(0.75f, 0.0f, 1.0f, 0.5f)
+
+ // create a buffer and put an unpremul value into it
+ val srcBuf = ByteBuffer.allocate(8)
+ srcBuf.order(ByteOrder.LITTLE_ENDIAN)
+ srcBuf.putShort(Half(unpremulColor.red()).halfValue())
+ srcBuf.putShort(Half(unpremulColor.green()).halfValue())
+ srcBuf.putShort(Half(unpremulColor.blue()).halfValue())
+ srcBuf.putShort(Half(unpremulColor.alpha()).halfValue())
+ srcBuf.rewind()
+
+ // create a bitmap with the unpremul value and set a colorspace to something that is
+ // different from the destination
+ val bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.RGBA_F16,
+ true, ColorSpace.get(ColorSpace.Named.BT2020))
+ bitmap.copyPixelsFromBuffer(srcBuf)
+ bitmap.setPremultiplied(false)
+
+ val bitmapShader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+ val shader = RuntimeShader(sampleComparisonShader)
+ shader.setFloatUniform("expectedSample", unpremulColor.components)
+
+ val rect = Rect(0, 0, 1, 1)
+ val paint = Paint()
+ paint.shader = shader
+ paint.blendMode = BlendMode.SRC
+
+ // Use setInputBuffer and let the shader verify that the sample contents were unaltered.
+ shader.setInputBuffer("inputShader", bitmapShader)
+ createTest().addCanvasClient(CanvasClient
+ { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+ true).runWithVerifier(RectVerifier(Color.WHITE, Color.GREEN, rect))
+
+ // Use setInputShader to treating it like a normal bitmap instead of data to verify
+ // everything is working as expected
+ shader.setInputShader("inputShader", bitmapShader)
+ createTest().addCanvasClient(CanvasClient
+ { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+ true).runWithVerifier(RectVerifier(Color.WHITE, Color.RED, rect))
+ }
+
+ @Test
+ fun testBasicColorUniform() {
+ val color = Color.valueOf(Color.BLUE).convert(ColorSpace.get(ColorSpace.Named.BT2020))
+ val shader = RuntimeShader(simpleColorShader)
+ shader.setColorUniform("inputColor", color)
+
+ val paint = Paint()
+ paint.shader = shader
+
+ val rect = Rect(10, 10, 80, 80)
+
+ createTest().addCanvasClient(CanvasClient
+ { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+ true).runWithVerifier(RectVerifier(Color.WHITE, Color.BLUE, rect))
+ }
+
+ @Test
+ fun testOpaqueConstructor() {
+ val shader = RuntimeShader(simpleColorShader, true)
+ Assert.assertTrue(shader.isForceOpaque)
+
+ val color = Color.valueOf(0.0f, 0.0f, 1.0f, 0.5f)
+ shader.setFloatUniform("inputNonColor", color.components)
+ shader.setIntUniform("useNonColor", 1)
+
+ val paint = Paint()
+ paint.shader = shader
+ paint.blendMode = BlendMode.SRC
+
+ val rect = Rect(10, 10, 80, 80)
+
+ createTest().addCanvasClient(CanvasClient
+ { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+ true).runWithVerifier(RectVerifier(Color.WHITE, Color.BLUE, rect))
+ }
+
+ @Test
+ fun testDrawThroughPicture() {
+ val rect = Rect(10, 10, 80, 80)
+ val picture = Picture()
+ run {
+ val paint = Paint()
+ paint.shader = RuntimeShader(simpleRedShader)
+
+ val canvas = picture.beginRecording(TEST_WIDTH, TEST_HEIGHT)
+ canvas.clipRect(rect)
+ canvas.drawPaint(paint)
+ picture.endRecording()
+ }
+ Assert.assertTrue(picture.requiresHardwareAcceleration())
+
+ createTest().addCanvasClient(CanvasClient
+ { canvas: Canvas, width: Int, height: Int -> canvas.drawPicture(picture) },
+ true).runWithVerifier(RectVerifier(Color.WHITE, Color.RED, rect))
+ }
+
+ @Test
+ fun testDrawThroughPictureWithComposeShader() {
+ val rect = Rect(10, 10, 80, 80)
+ val picture = Picture()
+ run {
+ val paint = Paint()
+ val runtimeShader = RuntimeShader(simpleRedShader)
+ paint.shader = ComposeShader(runtimeShader, bitmapShader, BlendMode.DST)
+
+ val canvas = picture.beginRecording(TEST_WIDTH, TEST_HEIGHT)
+ canvas.clipRect(rect)
+ canvas.drawPaint(paint)
+ picture.endRecording()
+ }
+ Assert.assertTrue(picture.requiresHardwareAcceleration())
+
+ createTest().addCanvasClient(CanvasClient
+ { canvas: Canvas, width: Int, height: Int -> canvas.drawPicture(picture) },
+ true).runWithVerifier(RectVerifier(Color.WHITE, Color.RED, rect))
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testDrawIntoSoftwareCanvas() {
+ val paint = Paint()
+ paint.shader = RuntimeShader(simpleRedShader)
+
+ val canvas = Canvas(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+ canvas.drawRect(0f, 0f, 10f, 10f, paint)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testDrawIntoSoftwareCanvasWithComposeShader() {
+ val paint = Paint()
+ paint.shader = ComposeShader(RuntimeShader(simpleRedShader), bitmapShader, BlendMode.SRC)
+
+ val canvas = Canvas(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+ canvas.drawRect(0f, 0f, 10f, 10f, paint)
+ }
+
+ val mSemiTransparentBlueShader = """
+ vec4 main(vec2 coord) {
+ return vec4(0.0, 0.0, 0.5, 0.5);
+ }"""
+ val simpleRedShader = """
+ vec4 main(vec2 coord) {
+ return vec4(1.0, 0.0, 0.0, 1.0);
+ }"""
+ val simpleColorShader = """
+ layout(color) uniform vec4 inputColor;
+ uniform vec4 inputNonColor;
+ uniform int useNonColor;
+ vec4 main(vec2 coord) {
+ vec4 outputColor = inputColor;
+ if (useNonColor != 0) {
+ outputColor = inputNonColor;
+ }
+ return outputColor;
+ }"""
+ val samplingShader = """
+ uniform shader inputShader;
+ vec4 main(vec2 coord) {
+ return inputShader.eval(coord).rgba;
+ }"""
+ val sampleComparisonShader = """
+ uniform shader inputShader;
+ uniform vec4 expectedSample;
+ vec4 main(vec2 coord) {
+ vec4 sampledValue = inputShader.eval(coord);
+ if (sampledValue == expectedSample) {
+ return vec4(0.0, 1.0, 0.0, 1.0);
+ }
+ return vec4(1.0, 0.0, 0.0, 1.0);
+ }"""
+ val simpleShader = """
+ uniform shader inputShader;
+ uniform float inputFloat;
+ uniform int inputInt;
+ vec4 main(vec2 coord) {
+ float alpha = float(100 - inputInt) / 100.0;
+ return vec4(inputShader.eval(coord).rgb * inputFloat, alpha);
+ }"""
+ val mBlackIfInputNotOpaqueShader = """
+ uniform shader inputShader;
+ vec4 main(vec2 coord) {
+ vec4 color = inputShader.eval(coord);
+ float multiplier = 1.0;
+ if (color.a != 1.0) {
+ multiplier = 0.0;
+ }
+ return vec4(color.rgb * multiplier, 1.0);
+ }"""
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java
index 5defe4b..7fa1441 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java
@@ -1,14 +1,16 @@
package android.uirendering.cts.testclasses;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Path;
import android.graphics.Rect;
import android.uirendering.cts.R;
+import android.uirendering.cts.bitmapverifiers.AntiAliasingVerifier;
import android.uirendering.cts.bitmapverifiers.BitmapVerifier;
import android.uirendering.cts.bitmapverifiers.RectVerifier;
+import android.uirendering.cts.bitmapverifiers.RegionVerifier;
import android.uirendering.cts.testclasses.view.UnclippedBlueView;
import android.uirendering.cts.testinfrastructure.ActivityTestBase;
import android.uirendering.cts.testinfrastructure.ViewInitializer;
@@ -35,7 +37,12 @@
static final Rect BOUNDS_RECT = new Rect(0, 0, 80, 80);
static final Rect PADDED_RECT = new Rect(15, 16, 63, 62);
static final Rect OUTLINE_RECT = new Rect(1, 2, 78, 79);
+ static final Rect ANTI_ALIAS_OUTLINE_RECT = new Rect(20, 10, 80, 80);
static final Rect CLIP_BOUNDS_RECT = new Rect(10, 20, 50, 60);
+ static final Rect CONCAVE_OUTLINE_RECT1 = new Rect(0, 0, 10, 90);
+ static final Rect CONCAVE_TEST_RECT1 = new Rect(0, 10, 90, 90);
+ static final Rect CONCAVE_OUTLINE_RECT2 = new Rect(0, 0, 90, 10);
+ static final Rect CONCAVE_TEST_RECT2 = new Rect(10, 0, 90, 90);
static final ViewInitializer BOUNDS_CLIP_INIT =
rootView -> ((ViewGroup)rootView).setClipChildren(true);
@@ -49,6 +56,7 @@
static final ViewInitializer OUTLINE_CLIP_INIT = rootView -> {
View child = rootView.findViewById(R.id.child);
+// ((ViewGroup)(child.getParent())).setBackgroundColor(Color.WHITE);
child.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
@@ -58,6 +66,48 @@
child.setClipToOutline(true);
};
+ static final ViewInitializer OUTLINE_CLIP_AA_INIT = rootView -> {
+ View child = rootView.findViewById(R.id.child);
+ ((ViewGroup) (child.getParent())).setBackgroundColor(Color.BLACK);
+ child.setOutlineProvider(new ViewOutlineProvider() {
+ Path mPath = new Path();
+ @Override
+ public void getOutline(View view, Outline outline) {
+ mPath.rewind();
+ // We're using the AA outline rect as a starting point, but shifting one of the
+ // vetices slightly to force AA for this not-quite-rectangle
+ mPath.moveTo(ANTI_ALIAS_OUTLINE_RECT.left, ANTI_ALIAS_OUTLINE_RECT.top);
+ mPath.lineTo(ANTI_ALIAS_OUTLINE_RECT.right, ANTI_ALIAS_OUTLINE_RECT.top);
+ mPath.lineTo(ANTI_ALIAS_OUTLINE_RECT.right, ANTI_ALIAS_OUTLINE_RECT.bottom);
+ mPath.lineTo(ANTI_ALIAS_OUTLINE_RECT.left + 1, ANTI_ALIAS_OUTLINE_RECT.bottom);
+ mPath.close();
+ outline.setPath(mPath);
+ }
+ });
+ child.setClipToOutline(true);
+ };
+
+ static final ViewInitializer CONCAVE_CLIP_INIT = rootView -> {
+ View child = rootView.findViewById(R.id.child);
+ ((ViewGroup) (child.getParent())).setBackgroundColor(Color.BLACK);
+ child.setOutlineProvider(new ViewOutlineProvider() {
+ Path mPath = new Path();
+ @Override
+ public void getOutline(View view, Outline outline) {
+ mPath.rewind();
+ mPath.addRect(CONCAVE_OUTLINE_RECT1.left, CONCAVE_OUTLINE_RECT1.top,
+ CONCAVE_OUTLINE_RECT1.right, CONCAVE_OUTLINE_RECT1.bottom,
+ Path.Direction.CW);
+ mPath.addRect(CONCAVE_OUTLINE_RECT2.left, CONCAVE_OUTLINE_RECT2.top,
+ CONCAVE_OUTLINE_RECT2.right, CONCAVE_OUTLINE_RECT2.bottom,
+ Path.Direction.CW);
+ outline.setPath(mPath);
+ assertTrue(outline.canClip());
+ }
+ });
+ child.setClipToOutline(true);
+ };
+
static final ViewInitializer CLIP_BOUNDS_CLIP_INIT =
view -> view.setClipBounds(CLIP_BOUNDS_RECT);
@@ -66,6 +116,18 @@
return new RectVerifier(Color.WHITE, Color.BLUE, blueBoundsRect, 75);
}
+ static BitmapVerifier makeConcaveClipVerifier() {
+ return new RegionVerifier()
+ .addVerifier(CONCAVE_TEST_RECT1, new RectVerifier(Color.BLACK, Color.BLUE,
+ CONCAVE_OUTLINE_RECT1, 75))
+ .addVerifier(CONCAVE_TEST_RECT2, new RectVerifier(Color.BLACK, Color.BLUE,
+ CONCAVE_OUTLINE_RECT2, 75));
+ }
+
+ static BitmapVerifier makeAAClipVerifier(Rect blueBoundsRect) {
+ return new AntiAliasingVerifier(Color.BLACK, Color.BLUE, blueBoundsRect);
+ }
+
@Test
public void testSimpleUnclipped() {
createTest()
@@ -109,10 +171,16 @@
}
@Test
+ public void testAntiAliasedOutlineClip() {
+ // NOTE: Only HW is supported
+ createTest()
+ .addLayout(R.layout.blue_padded_layout, OUTLINE_CLIP_AA_INIT, true)
+ .runWithVerifier(makeAAClipVerifier(ANTI_ALIAS_OUTLINE_RECT));
+ }
+
+ @Test
public void testOvalOutlineClip() {
- // In hw this works because clipping to an arbitrary path (i.e. not a rectangle, round rect
- // or circle) isn't supported, and is no-op'd.
- // In sw this works because Outline clipping isn't supported.
+ // As of Android T, Outline clipping is enabled for all shapes.
createTest()
.addLayout(R.layout.blue_padded_layout, view -> {
view.setOutlineProvider(new ViewOutlineProvider() {
@@ -123,10 +191,10 @@
mPath.addOval(0, 0, view.getWidth(), view.getHeight(),
Path.Direction.CW);
outline.setPath(mPath);
- assertFalse(outline.canClip());
+ assertTrue(outline.canClip());
}
});
- view.setClipToOutline(true); // should do nothing
+ view.setClipToOutline(false); // should do nothing
})
.runWithVerifier(makeClipVerifier(FULL_RECT));
}
@@ -137,23 +205,9 @@
// path, but it does not result in clipping, which is only supported when explicitly calling
// one of the other setters. (hw no-op's the arbitrary path, and sw doesn't support Outline
// clipping.)
+ // As of T, path clipping is enabled for all Outline shapes.
createTest()
- .addLayout(R.layout.blue_padded_layout, view -> {
- view.setOutlineProvider(new ViewOutlineProvider() {
- Path mPath = new Path();
- @Override
- public void getOutline(View view, Outline outline) {
- mPath.reset();
- mPath.addRect(0, 0, 10, 100, Path.Direction.CW);
- mPath.addRect(0, 0, 100, 10, Path.Direction.CW);
- assertFalse(mPath.isConvex());
-
- outline.setPath(mPath);
- assertFalse(outline.canClip());
- }
- });
- view.setClipToOutline(true); // should do nothing
- })
- .runWithVerifier(makeClipVerifier(FULL_RECT));
+ .addLayout(R.layout.blue_padded_layout, CONCAVE_CLIP_INIT, true)
+ .runWithVerifier(makeConcaveClipVerifier());
}
}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.kt b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.kt
index a5d40cf..7a91ab9 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.kt
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.kt
@@ -82,6 +82,7 @@
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_FULLSCREEN
+ window.decorView.keepScreenOn = true
mHandler = RenderSpecHandler()
setContentView(R.layout.test_container)
@@ -119,7 +120,6 @@
} catch (e: InterruptedException) {
throw AssertionError(e)
}
-
}
assertNotNull("Timeout waiting for draw", mPositionInfo)
return mPositionInfo!!
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/util/CompareUtils.java b/tests/tests/uirendering/src/android/uirendering/cts/util/CompareUtils.java
index c80a778..de7d149 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/util/CompareUtils.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/util/CompareUtils.java
@@ -25,4 +25,16 @@
&& Math.abs(Color.green(color) - average) <= threshold
&& Math.abs(Color.blue(color) - average) <= threshold;
}
+
+ /**
+ * @return True if color strictly between inner and outer colors. This verifies that the
+ * color is a mixture of the two, not just one or the other (for anti-aliased pixels).
+ */
+ public static boolean verifyPixelBetweenColors(int color, int expectedOuterColor,
+ int expectedInnerColor) {
+ if (color == expectedInnerColor || color == expectedOuterColor) {
+ return false;
+ }
+ return color == ((color & expectedInnerColor) | (color & expectedOuterColor));
+ }
}
diff --git a/tests/tests/uirendering27/src/android/uirendering/cts/testclasses/ExpandingClipTest.java b/tests/tests/uirendering27/src/android/uirendering/cts/testclasses/ExpandingClipTest.java
new file mode 100644
index 0000000..eceb056
--- /dev/null
+++ b/tests/tests/uirendering27/src/android/uirendering/cts/testclasses/ExpandingClipTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Region;
+import android.uirendering.cts.bitmapverifiers.ColorVerifier;
+import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ExpandingClipTest extends ActivityTestBase {
+
+ @Test
+ public void testClipReplace() {
+ // The replace op should function correctly on apps compatible with version 27
+ Point[] testPoints = {
+ new Point(0, 5),
+ new Point(10, 5)
+ };
+ int[] colors = {
+ Color.WHITE,
+ Color.BLUE
+ };
+ createTest()
+ .addCanvasClient((canvas, width, height) -> {
+ Paint paint = new Paint();
+ paint.setColor(Color.WHITE);
+ canvas.drawRect(0, 0, width, height, paint);
+
+ // These are exclusive, but if the replace op properly
+ // removes the earlier intersect, only the right portion of
+ // the canvas will be filled with blue.
+ canvas.clipRect(0, 0, 5, height);
+ canvas.clipRect(5, 0, width, height, Region.Op.REPLACE);
+
+ paint.setColor(Color.BLUE);
+ canvas.drawRect(0, 0, width, height, paint);
+ })
+ .runWithVerifier(new SamplePointVerifier(testPoints, colors));
+ }
+
+ @Test
+ public void testClipUnsupportedExpandingOps() {
+ // Test that other expanding clip ops are silently ignored for v27
+ createTest()
+ .addCanvasClient((canvas, width, height) -> {
+ // Should not modify clip state so later draw fills bitmap
+ canvas.clipRect(0, 0, 5, 5, Region.Op.REVERSE_DIFFERENCE);
+ canvas.clipRect(5, 5, 10, 10, Region.Op.XOR);
+ canvas.clipRect(10, 10, 15, 15, Region.Op.UNION);
+
+ Paint paint = new Paint();
+ paint.setColor(Color.RED);
+ canvas.drawRect(0, 0, width, height, paint);
+ })
+ .runWithVerifier(new ColorVerifier(Color.RED));
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/util/src/android/util/cts/ArrayMapTest.java b/tests/tests/util/src/android/util/cts/ArrayMapTest.java
index b70166b..377182f 100644
--- a/tests/tests/util/src/android/util/cts/ArrayMapTest.java
+++ b/tests/tests/util/src/android/util/cts/ArrayMapTest.java
@@ -46,6 +46,7 @@
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
+import java.util.function.BiFunction;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -635,6 +636,20 @@
}
}
+ @Test
+ public void testForEach() {
+ ArrayMap<String, Integer> map = new ArrayMap<>();
+
+ for (int i = 0; i < 50; ++i) {
+ map.put(Integer.toString(i), i * 10);
+ }
+
+ // Make sure forEach goes through all of the elements.
+ HashMap<String, Integer> seen = new HashMap<>();
+ map.forEach(seen::put);
+ compareMaps(seen, map);
+ }
+
/**
* The entrySet Iterator returns itself from each call to {@code next()}.
* This is unusual behavior for {@link Iterator#next()}; this test ensures that
@@ -699,4 +714,20 @@
fail("ArrayMap removeAll failure, expect empty, but " + map);
}
}
+
+ @Test
+ public void testReplaceAll() {
+ final ArrayMap<Integer, Integer> map = new ArrayMap<>();
+ final ArrayMap<Integer, Integer> expectedMap = new ArrayMap<>();
+ final BiFunction<Integer, Integer, Integer> function = (k, v) -> 2 * v;
+ for (Integer i : Arrays.asList(0, 1, 2, 3, 4, 5)) {
+ map.put(i, i);
+ expectedMap.put(i, 2 * i);
+ }
+
+ map.replaceAll(function);
+ if (!compare(map, expectedMap)) {
+ fail("ArrayMap replaceAll failure, expect " + expectedMap + ", but " + map);
+ }
+ }
}
diff --git a/tests/tests/util/src/android/util/cts/ArraySetTest.java b/tests/tests/util/src/android/util/cts/ArraySetTest.java
index 70d307a..42309e3 100644
--- a/tests/tests/util/src/android/util/cts/ArraySetTest.java
+++ b/tests/tests/util/src/android/util/cts/ArraySetTest.java
@@ -412,6 +412,20 @@
}
@Test
+ public void testForEach() {
+ ArraySet<Integer> set = new ArraySet<>();
+
+ for (int i = 0; i < 50; ++i) {
+ set.add(i * 10);
+ }
+
+ // Make sure forEach goes through all of the elements.
+ HashSet<Integer> seen = new HashSet<>();
+ set.forEach(seen::add);
+ compareSets(seen, set);
+ }
+
+ @Test
public void testIndexOf() {
ArraySet<Integer> set = new ArraySet<>();
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index a892da1..17e8889 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -411,7 +411,7 @@
</intent-filter>
</activity>
- <activity android:name="android.view.cts.InputDeviceKeyLayoutMapTestActivity"
+ <activity android:name="android.view.cts.input.InputDeviceKeyLayoutMapTestActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -461,6 +461,13 @@
</intent-filter>
</activity>
+ <activity android:name="android.view.cts.InputQueueCtsActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ </intent-filter>
+ </activity>
+
<!-- Overrides the activity declaration in AndroidX test library to remove the starting
animation. -->
<activity
@@ -468,6 +475,14 @@
tools:replace="android:theme"
android:theme="@style/WhiteBackgroundTheme" />
+ <activity android:name="android.view.cts.OnBackInvokedDispatcherTestActivity"
+ android:exported="true">
+ <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.view.textclassifier.cts.CtsTextClassifierService"
android:exported="true"
android:permission="android.permission.BIND_TEXTCLASSIFIER_SERVICE">
diff --git a/tests/tests/view/OWNERS b/tests/tests/view/OWNERS
index 7074017..0ece2bb 100644
--- a/tests/tests/view/OWNERS
+++ b/tests/tests/view/OWNERS
@@ -1,6 +1,6 @@
# Bug component: 25700
adamp@google.com
-shepshapard@google.com
clarabayarri@google.com
jreck@google.com
-njawad@google.com
\ No newline at end of file
+njawad@google.com
+svv@google.com
diff --git a/tests/tests/view/jni/Android.bp b/tests/tests/view/jni/Android.bp
index 107650e..de254ed 100644
--- a/tests/tests/view/jni/Android.bp
+++ b/tests/tests/view/jni/Android.bp
@@ -33,6 +33,7 @@
"android_view_cts_ASurfaceControlTest.cpp",
"android_view_cts_ChoreographerNativeTest.cpp",
"android_view_cts_InputDeviceKeyLayoutMapTest.cpp",
+ "android_view_cts_InputQueueTest.cpp",
],
shared_libs: [
diff --git a/tests/tests/view/jni/ChoreographerTestUtils.h b/tests/tests/view/jni/ChoreographerTestUtils.h
new file mode 100644
index 0000000..0a66f28
--- /dev/null
+++ b/tests/tests/view/jni/ChoreographerTestUtils.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <android/choreographer.h>
+#include <android/log.h>
+#include <android/looper.h>
+#include <android/trace.h>
+#include <jni.h>
+#include <jniAssert.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include <chrono>
+#include <cinttypes>
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+#include <limits>
+#include <mutex>
+#include <set>
+#include <sstream>
+#include <string>
+#include <thread>
+#include <tuple>
+#include <vector>
+
+using namespace std::chrono_literals;
+static constexpr std::chrono::nanoseconds NOMINAL_VSYNC_PERIOD{16ms};
+static constexpr std::chrono::nanoseconds DELAY_PERIOD{NOMINAL_VSYNC_PERIOD * 5};
+static constexpr std::chrono::nanoseconds ZERO{std::chrono::nanoseconds::zero()};
+
+#define NANOS_PER_SECOND 1000000000LL
+static int64_t systemTime() {
+ struct timespec time;
+ int result = clock_gettime(CLOCK_MONOTONIC, &time);
+ if (result < 0) {
+ return -errno;
+ }
+ return (time.tv_sec * NANOS_PER_SECOND) + time.tv_nsec;
+}
+
+static std::mutex gLock;
+struct Callback {
+ Callback(const char* name) : name(name) {}
+ std::string name;
+ int count{0};
+ std::chrono::nanoseconds frameTime{0LL};
+};
+
+struct ExtendedCallback : Callback {
+ ExtendedCallback(const char* name, JNIEnv* env) : Callback(name), env(env) {}
+
+ struct FrameTime {
+ FrameTime(const AChoreographerFrameCallbackData* callbackData, int index)
+ : vsyncId(AChoreographerFrameCallbackData_getFrameTimelineVsyncId(callbackData,
+ index)),
+ expectedPresentTime(
+ AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTimeNanos(
+ callbackData, index)),
+ deadline(AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(callbackData,
+ index)) {}
+
+ const int64_t vsyncId{-1};
+ const int64_t expectedPresentTime{-1};
+ const int64_t deadline{-1};
+ };
+
+ void populate(const AChoreographerFrameCallbackData* callbackData) {
+ size_t index = AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(callbackData);
+ preferredFrameTimelineIndex = index;
+
+ size_t length = AChoreographerFrameCallbackData_getFrameTimelinesLength(callbackData);
+ {
+ std::lock_guard<std::mutex> _l{gLock};
+ ASSERT(length >= 1, "Frame timelines should not be empty");
+ ASSERT(index < length, "Frame timeline index must be less than length");
+ }
+ timeline.reserve(length);
+
+ for (int i = 0; i < length; i++) {
+ timeline.push_back(FrameTime(callbackData, i));
+ }
+ }
+
+ size_t getPreferredFrameTimelineIndex() const { return preferredFrameTimelineIndex; }
+ const std::vector<FrameTime>& getTimeline() const { return timeline; }
+
+private:
+ JNIEnv* env;
+ size_t preferredFrameTimelineIndex{std::numeric_limits<size_t>::max()};
+ std::vector<FrameTime> timeline;
+};
+
+static void extendedFrameCallback(int64_t frameTimeNanos, void* data) {
+ std::lock_guard<std::mutex> _l(gLock);
+ ATrace_beginSection("extendedFrameCallback base");
+ Callback* cb = static_cast<Callback*>(data);
+ cb->count++;
+ cb->frameTime = std::chrono::nanoseconds{frameTimeNanos};
+ ATrace_endSection();
+}
+
+static void extendedFrameCallback(const AChoreographerFrameCallbackData* callbackData, void* data) {
+ ATrace_beginSection("extendedFrameCallback");
+ extendedFrameCallback(AChoreographerFrameCallbackData_getFrameTimeNanos(callbackData), data);
+
+ ExtendedCallback* cb = static_cast<ExtendedCallback*>(data);
+ cb->populate(callbackData);
+ ATrace_endSection();
+}
+
+static void frameCallback64(int64_t frameTimeNanos, void* data) {
+ extendedFrameCallback(frameTimeNanos, data);
+}
+
+static void frameCallback(long frameTimeNanos, void* data) {
+ extendedFrameCallback((int64_t)frameTimeNanos, data);
+}
+
+static std::chrono::nanoseconds now() {
+ return std::chrono::steady_clock::now().time_since_epoch();
+}
+
+static void verifyCallback(JNIEnv* env, const Callback& cb, int expectedCount,
+ std::chrono::nanoseconds startTime, std::chrono::nanoseconds maxTime) {
+ std::lock_guard<std::mutex> _l{gLock};
+ ASSERT(cb.count == expectedCount, "Choreographer failed to invoke '%s' %d times - actual: %d",
+ cb.name.c_str(), expectedCount, cb.count);
+ if (maxTime > ZERO) {
+ auto duration = cb.frameTime - startTime;
+ ASSERT(duration < maxTime, "Callback '%s' has incorrect frame time in invocation %d",
+ cb.name.c_str(), expectedCount);
+ }
+}
diff --git a/tests/tests/view/jni/CtsViewJniOnLoad.cpp b/tests/tests/view/jni/CtsViewJniOnLoad.cpp
index 2d4b3d7..92d57b4 100644
--- a/tests/tests/view/jni/CtsViewJniOnLoad.cpp
+++ b/tests/tests/view/jni/CtsViewJniOnLoad.cpp
@@ -23,6 +23,7 @@
extern int register_android_view_cts_AKeyEventNativeTest(JNIEnv *env);
extern int register_android_view_cts_AMotionEventNativeTest(JNIEnv *env);
extern int register_android_view_cts_InputDeviceKeyLayoutMapTest(JNIEnv *env);
+extern int register_android_view_cts_InputQueueTest(JNIEnv *env);
jint JNI_OnLoad(JavaVM *vm, void *) {
JNIEnv *env = NULL;
@@ -44,5 +45,8 @@
if (register_android_view_cts_InputDeviceKeyLayoutMapTest(env)) {
return JNI_ERR;
}
+ if (register_android_view_cts_InputQueueTest(env)) {
+ return JNI_ERR;
+ }
return JNI_VERSION_1_4;
}
diff --git a/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp b/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
index f315fbc..95f61c9 100644
--- a/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
+++ b/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
@@ -16,12 +16,17 @@
#define LOG_TAG "ASurfaceControlTest"
+#include <ChoreographerTestUtils.h>
+#include <android/choreographer.h>
#include <android/data_space.h>
#include <android/hardware_buffer.h>
+#include <android/hardware_buffer_jni.h>
#include <android/log.h>
+#include <android/looper.h>
#include <android/native_window_jni.h>
#include <android/surface_control.h>
#include <android/sync.h>
+#include <android/trace.h>
#include <errno.h>
#include <jni.h>
#include <poll.h>
@@ -43,16 +48,6 @@
jmethodID onTransactionComplete;
} gTransactionCompleteListenerClassInfo;
-#define NANOS_PER_SECOND 1000000000LL
-int64_t systemTime() {
- struct timespec time;
- int result = clock_gettime(CLOCK_MONOTONIC, &time);
- if (result < 0) {
- return -errno;
- }
- return (time.tv_sec * NANOS_PER_SECOND) + time.tv_nsec;
-}
-
static AHardwareBuffer* allocateBuffer(int32_t width, int32_t height) {
AHardwareBuffer* buffer = nullptr;
AHardwareBuffer_Desc desc = {};
@@ -98,6 +93,7 @@
AHardwareBuffer_lock(buffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, -1, &rect,
&data);
if (!data) {
+ AHardwareBuffer_release(buffer);
return true;
}
@@ -109,6 +105,16 @@
return false;
}
+jobject Utils_getSolidBuffer(JNIEnv* env, jobject /*clazz*/, jint width, jint height, jint color) {
+ AHardwareBuffer* buffer;
+ if (getSolidBuffer(width, height, static_cast<uint32_t>(color), &buffer, nullptr)) {
+ return nullptr;
+ }
+ jobject result = AHardwareBuffer_toHardwareBuffer(env, buffer);
+ AHardwareBuffer_release(buffer);
+ return result;
+}
+
static bool getQuadrantBuffer(int32_t width, int32_t height, jint colorTopLeft,
jint colorTopRight, jint colorBottomRight,
jint colorBottomLeft,
@@ -143,6 +149,19 @@
return false;
}
+jobject Utils_getQuadrantBuffer(JNIEnv* env, jobject /*clazz*/, jint width, jint height,
+ jint colorTopLeft, jint colorTopRight, jint colorBottomRight,
+ jint colorBottomLeft) {
+ AHardwareBuffer* buffer;
+ if (getQuadrantBuffer(width, height, colorTopLeft, colorTopRight, colorBottomRight,
+ colorBottomLeft, &buffer, nullptr)) {
+ return nullptr;
+ }
+ jobject result = AHardwareBuffer_toHardwareBuffer(env, buffer);
+ AHardwareBuffer_release(buffer);
+ return result;
+}
+
jlong SurfaceTransaction_create(JNIEnv* /*env*/, jclass) {
return reinterpret_cast<jlong>(ASurfaceTransaction_create());
}
@@ -544,6 +563,77 @@
nullptr, transactionCallbackWithoutContextThunk);
}
+void SurfaceTransaction_setFrameTimeline(JNIEnv* /*env*/, jclass, jlong surfaceTransaction,
+ jlong vsyncId) {
+ ASurfaceTransaction_setFrameTimeline(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+ vsyncId);
+}
+
+static struct {
+ jclass clazz;
+ jmethodID constructor;
+} gFrameTimelineClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID constructor;
+} gFrameCallbackDataClassInfo;
+
+static void verifyChoreographer(JNIEnv* env, AChoreographer* choreographer) {
+ ASSERT(choreographer != nullptr, "Choreographer setup unsuccessful");
+}
+
+static void verifyPollCallback(JNIEnv* env, int result) {
+ ASSERT(result == ALOOPER_POLL_CALLBACK, "Callback failed with error: %d", result);
+}
+
+/** Gets VSync information from Choreographer, including a collection of frame timelines and
+ * platform-preferred index using Choreographer. */
+jobject SurfaceControlTest_getFrameTimelines(JNIEnv* env, jclass) {
+ ALooper_prepare(0);
+ ATrace_beginSection("Getting Choreographer instance");
+ AChoreographer* choreographer = AChoreographer_getInstance();
+ ATrace_endSection();
+ verifyChoreographer(env, choreographer);
+
+ ExtendedCallback cb1("cb1", env);
+ auto start = now();
+ ATrace_beginSection("postExtendedFrameCallback");
+ AChoreographer_postExtendedFrameCallback(choreographer, extendedFrameCallback, &cb1);
+ ATrace_endSection();
+ auto delayPeriod = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count();
+ ATrace_beginSection("ALooper_pollOnce");
+ int result = ALooper_pollOnce(delayPeriod * 5, nullptr, nullptr, nullptr);
+ ATrace_endSection();
+ verifyPollCallback(env, result);
+ verifyCallback(env, cb1, 1, start, NOMINAL_VSYNC_PERIOD * 3);
+
+ jobjectArray frameTimelineObjs =
+ env->NewObjectArray(cb1.getTimeline().size(), gFrameTimelineClassInfo.clazz,
+ /*initial element*/ NULL);
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return NULL;
+ }
+ if (frameTimelineObjs == NULL) {
+ jniThrowRuntimeException(env, "Failed to create FrameTimeline array");
+ return NULL;
+ }
+ for (int i = 0; i < cb1.getTimeline().size(); i++) {
+ ExtendedCallback::FrameTime frameTimeline = cb1.getTimeline()[i];
+ jobject frameTimelineObj =
+ env->NewObject(gFrameTimelineClassInfo.clazz, gFrameTimelineClassInfo.constructor,
+ frameTimeline.vsyncId, frameTimeline.expectedPresentTime,
+ frameTimeline.deadline);
+ env->SetObjectArrayElement(frameTimelineObjs, i, frameTimelineObj);
+ }
+
+ return env->NewObject(gFrameCallbackDataClassInfo.clazz,
+ gFrameCallbackDataClassInfo.constructor, frameTimelineObjs,
+ cb1.getPreferredFrameTimelineIndex());
+}
+
static const JNINativeMethod JNI_METHODS[] = {
{"nSurfaceTransaction_create", "()J", (void*)SurfaceTransaction_create},
{"nSurfaceTransaction_delete", "(J)V", (void*)SurfaceTransaction_delete},
@@ -591,6 +681,16 @@
{"nSurfaceTransaction_setOnCommitCallbackWithoutContext",
"(JLandroid/view/cts/util/ASurfaceControlTestUtils$TransactionCompleteListener;)V",
(void*)SurfaceTransaction_setOnCommitCallbackWithoutContext},
+ {"nSurfaceTransaction_setFrameTimeline", "(JJ)V",
+ (void*)SurfaceTransaction_setFrameTimeline},
+ {"getSolidBuffer", "(III)Landroid/hardware/HardwareBuffer;", (void*)Utils_getSolidBuffer},
+ {"getQuadrantBuffer", "(IIIIII)Landroid/hardware/HardwareBuffer;",
+ (void*)Utils_getQuadrantBuffer},
+};
+
+static const JNINativeMethod FRAME_TIMELINE_JNI_METHODS[] = {
+ {"nGetFrameTimelines", "()Landroid/view/cts/util/FrameCallbackData;",
+ (void*)SurfaceControlTest_getFrameTimelines},
};
} // anonymous namespace
@@ -602,6 +702,22 @@
static_cast<jclass>(env->NewGlobalRef(transactionCompleteListenerClazz));
gTransactionCompleteListenerClassInfo.onTransactionComplete =
env->GetMethodID(transactionCompleteListenerClazz, "onTransactionComplete", "(JJ)V");
+
+ gFrameTimelineClassInfo.clazz = static_cast<jclass>(env->NewGlobalRef(
+ env->FindClass("android/view/cts/util/FrameCallbackData$FrameTimeline")));
+ gFrameTimelineClassInfo.constructor =
+ env->GetMethodID(gFrameTimelineClassInfo.clazz, "<init>", "(JJJ)V");
+
+ gFrameCallbackDataClassInfo.clazz = static_cast<jclass>(
+ env->NewGlobalRef(env->FindClass("android/view/cts/util/FrameCallbackData")));
+ gFrameCallbackDataClassInfo.constructor =
+ env->GetMethodID(gFrameCallbackDataClassInfo.clazz, "<init>",
+ "([Landroid/view/cts/util/FrameCallbackData$FrameTimeline;I)V");
+
+ jclass frameCallbackDataClass = env->FindClass("android/view/cts/util/FrameCallbackData");
+ env->RegisterNatives(frameCallbackDataClass, FRAME_TIMELINE_JNI_METHODS,
+ sizeof(FRAME_TIMELINE_JNI_METHODS) / sizeof(JNINativeMethod));
+
jclass clazz = env->FindClass("android/view/cts/util/ASurfaceControlTestUtils");
return env->RegisterNatives(clazz, JNI_METHODS, sizeof(JNI_METHODS) / sizeof(JNINativeMethod));
}
diff --git a/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp b/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
index 6206418..a7443ed 100644
--- a/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
+++ b/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
@@ -15,6 +15,7 @@
*
*/
+#include <ChoreographerTestUtils.h>
#include <android/choreographer.h>
#include <android/looper.h>
#include <jni.h>
@@ -25,27 +26,20 @@
#include <cmath>
#include <cstdlib>
#include <cstring>
+#include <limits>
#include <mutex>
#include <set>
#include <sstream>
#include <string>
#include <thread>
+#include <tuple>
+#include <vector>
#define LOG_TAG "ChoreographerNativeTest"
-#define ASSERT(condition, format, args...) \
- if (!(condition)) { \
- fail(env, format, ## args); \
- return; \
- }
-
using namespace std::chrono_literals;
-static constexpr std::chrono::nanoseconds NOMINAL_VSYNC_PERIOD{16ms};
-static constexpr std::chrono::nanoseconds DELAY_PERIOD{NOMINAL_VSYNC_PERIOD * 5};
-static constexpr std::chrono::nanoseconds ZERO{std::chrono::nanoseconds::zero()};
-
struct {
struct {
jclass clazz;
@@ -53,14 +47,7 @@
} choreographerNativeTest;
} gJni;
-static std::mutex gLock;
static std::set<int64_t> gSupportedRefreshPeriods;
-struct Callback {
- Callback(const char* name): name(name) {}
- std::string name;
- int count{0};
- std::chrono::nanoseconds frameTime{0LL};
-};
struct RefreshRateCallback {
RefreshRateCallback(const char* name): name(name) {}
@@ -79,17 +66,6 @@
std::chrono::nanoseconds vsyncPeriod{0LL};
};
-static void frameCallback64(int64_t frameTimeNanos, void* data) {
- std::lock_guard<std::mutex> _l(gLock);
- Callback* cb = static_cast<Callback*>(data);
- cb->count++;
- cb->frameTime = std::chrono::nanoseconds{frameTimeNanos};
-}
-
-static void frameCallback(long frameTimeNanos, void* data) {
- frameCallback64((int64_t) frameTimeNanos, data);
-}
-
static void refreshRateCallback(int64_t vsyncPeriodNanos, void* data) {
std::lock_guard<std::mutex> _l(gLock);
RefreshRateCallback* cb = static_cast<RefreshRateCallback*>(data);
@@ -108,37 +84,6 @@
static_cast<int>(std::round(1e9f / cb->vsyncPeriod.count())));
}
-static std::chrono::nanoseconds now() {
- return std::chrono::steady_clock::now().time_since_epoch();
-}
-
-static void fail(JNIEnv* env, const char* format, ...) {
- va_list args;
-
- va_start(args, format);
- char *msg;
- int rc = vasprintf(&msg, format, args);
- va_end(args);
-
- jclass exClass;
- const char *className = "java/lang/AssertionError";
- exClass = env->FindClass(className);
- env->ThrowNew(exClass, msg);
- free(msg);
-}
-
-static void verifyCallback(JNIEnv* env, const Callback& cb, int expectedCount,
- std::chrono::nanoseconds startTime, std::chrono::nanoseconds maxTime) {
- std::lock_guard<std::mutex> _l{gLock};
- ASSERT(cb.count == expectedCount, "Choreographer failed to invoke '%s' %d times - actual: %d",
- cb.name.c_str(), expectedCount, cb.count);
- if (maxTime > ZERO) {
- auto duration = cb.frameTime - startTime;
- ASSERT(duration < maxTime, "Callback '%s' has incorrect frame time in invocation %d",
- cb.name.c_str(), expectedCount);
- }
-}
-
static std::string dumpSupportedRefreshPeriods() {
std::stringstream ss;
ss << "{ ";
@@ -190,6 +135,101 @@
return choreographer != nullptr;
}
+static void
+android_view_cts_ChoreographerNativeTest_testPostExtendedCallbackWithoutDelayEventuallyRunsCallback(
+ JNIEnv* env, jclass, jlong choreographerPtr) {
+ AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
+ ExtendedCallback cb1("cb1", env);
+ ExtendedCallback cb2("cb2", env);
+ auto start = now();
+
+ AChoreographer_postExtendedFrameCallback(choreographer, extendedFrameCallback, &cb1);
+ AChoreographer_postExtendedFrameCallback(choreographer, extendedFrameCallback, &cb2);
+ std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
+
+ verifyCallback(env, cb1, 1, start, NOMINAL_VSYNC_PERIOD * 3);
+ verifyCallback(env, cb2, 1, start, NOMINAL_VSYNC_PERIOD * 3);
+ {
+ std::lock_guard<std::mutex> _l{gLock};
+ auto delta = cb2.frameTime - cb1.frameTime;
+ ASSERT(delta == ZERO || delta > ZERO && delta < NOMINAL_VSYNC_PERIOD * 2,
+ "Callback 1 and 2 have frame times too large of a delta in frame times");
+ }
+
+ AChoreographer_postExtendedFrameCallback(choreographer, extendedFrameCallback, &cb1);
+ start = now();
+ std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
+ verifyCallback(env, cb1, 2, start, NOMINAL_VSYNC_PERIOD * 3);
+ verifyCallback(env, cb2, 1, start, ZERO);
+}
+
+static void android_view_cts_ChoreographerNativeTest_testFrameCallbackDataVsyncIdValid(
+ JNIEnv* env, jclass, jlong choreographerPtr) {
+ AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
+ ExtendedCallback cb1("cb1", env);
+ auto start = now();
+
+ AChoreographer_postExtendedFrameCallback(choreographer, extendedFrameCallback, &cb1);
+ std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
+
+ verifyCallback(env, cb1, 1, start, NOMINAL_VSYNC_PERIOD * 3);
+ std::lock_guard<std::mutex> _l{gLock};
+ for (const ExtendedCallback::FrameTime& frameTime : cb1.getTimeline()) {
+ int64_t vsyncId = frameTime.vsyncId;
+ ASSERT(vsyncId >= 0, "Invalid vsync ID");
+ ASSERT(std::count_if(cb1.getTimeline().begin(), cb1.getTimeline().end(),
+ [vsyncId](const ExtendedCallback::FrameTime& ft) {
+ return ft.vsyncId == vsyncId;
+ }) == 1,
+ "Vsync ID is not unique");
+ }
+}
+
+static void android_view_cts_ChoreographerNativeTest_testFrameCallbackDataDeadlineInFuture(
+ JNIEnv* env, jclass, jlong choreographerPtr) {
+ AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
+ ExtendedCallback cb1("cb1", env);
+ auto start = now();
+
+ AChoreographer_postExtendedFrameCallback(choreographer, extendedFrameCallback, &cb1);
+ std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
+
+ verifyCallback(env, cb1, 1, start, NOMINAL_VSYNC_PERIOD * 3);
+ std::lock_guard<std::mutex> _l{gLock};
+ for (auto [i, lastValue] = std::tuple{0, cb1.frameTime}; i < cb1.getTimeline().size(); i++) {
+ auto deadline = std::chrono::nanoseconds{cb1.getTimeline()[i].deadline};
+ ASSERT(deadline > std::chrono::nanoseconds{start}, "Deadline must be after start time");
+ ASSERT(deadline > cb1.frameTime, "Deadline must be after frame time");
+ ASSERT(deadline > lastValue, "Deadline must be greater than last frame deadline");
+ lastValue = deadline;
+ }
+}
+
+static void
+android_view_cts_ChoreographerNativeTest_testFrameCallbackDataExpectedPresentTimeInFuture(
+ JNIEnv* env, jclass, jlong choreographerPtr) {
+ AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
+ ExtendedCallback cb1("cb1", env);
+ auto start = now();
+
+ AChoreographer_postExtendedFrameCallback(choreographer, extendedFrameCallback, &cb1);
+ std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
+
+ verifyCallback(env, cb1, 1, start, NOMINAL_VSYNC_PERIOD * 3);
+ std::lock_guard<std::mutex> _l{gLock};
+ for (auto [i, lastValue] = std::tuple{0, cb1.frameTime}; i < cb1.getTimeline().size(); i++) {
+ auto expectedPresentTime =
+ std::chrono::nanoseconds(cb1.getTimeline()[i].expectedPresentTime);
+ auto deadline = std::chrono::nanoseconds(cb1.getTimeline()[i].deadline);
+ ASSERT(expectedPresentTime > cb1.frameTime,
+ "Expected present time must be after frame time");
+ ASSERT(expectedPresentTime > deadline, "Expected present time must be after deadline");
+ ASSERT(expectedPresentTime > lastValue,
+ "Expected present time must be greater than last frame expected present time");
+ lastValue = expectedPresentTime;
+ }
+}
+
static void android_view_cts_ChoreographerNativeTest_testPostCallback64WithoutDelayEventuallyRunsCallback(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
@@ -510,6 +550,14 @@
(void*)android_view_cts_ChoreographerNativeTest_getChoreographer},
{"nativePrepareChoreographerTests", "(J[J)Z",
(void*)android_view_cts_ChoreographerNativeTest_prepareChoreographerTests},
+ {"nativeTestPostExtendedCallbackWithoutDelayEventuallyRunsCallbacks", "(J)V",
+ (void*)android_view_cts_ChoreographerNativeTest_testPostExtendedCallbackWithoutDelayEventuallyRunsCallback},
+ {"nativeTestFrameCallbackDataVsyncIdValid", "(J)V",
+ (void*)android_view_cts_ChoreographerNativeTest_testFrameCallbackDataVsyncIdValid},
+ {"nativeTestFrameCallbackDataDeadlineInFuture", "(J)V",
+ (void*)android_view_cts_ChoreographerNativeTest_testFrameCallbackDataDeadlineInFuture},
+ {"nativeTestFrameCallbackDataExpectedPresentTimeInFuture", "(J)V",
+ (void*)android_view_cts_ChoreographerNativeTest_testFrameCallbackDataExpectedPresentTimeInFuture},
{"nativeTestPostCallback64WithoutDelayEventuallyRunsCallbacks", "(J)V",
(void*)android_view_cts_ChoreographerNativeTest_testPostCallback64WithoutDelayEventuallyRunsCallback},
{"nativeTestPostCallback64WithDelayEventuallyRunsCallbacks", "(J)V",
diff --git a/tests/tests/view/jni/android_view_cts_InputDeviceKeyLayoutMapTest.cpp b/tests/tests/view/jni/android_view_cts_InputDeviceKeyLayoutMapTest.cpp
index 4d2c810..8f25d39 100644
--- a/tests/tests/view/jni/android_view_cts_InputDeviceKeyLayoutMapTest.cpp
+++ b/tests/tests/view/jni/android_view_cts_InputDeviceKeyLayoutMapTest.cpp
@@ -107,6 +107,6 @@
};
int register_android_view_cts_InputDeviceKeyLayoutMapTest(JNIEnv* env) {
- jclass clazz = env->FindClass("android/view/cts/InputDeviceKeyLayoutMapTest");
+ jclass clazz = env->FindClass("android/view/cts/input/InputDeviceKeyLayoutMapTest");
return env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod));
}
diff --git a/tests/tests/view/jni/android_view_cts_InputQueueTest.cpp b/tests/tests/view/jni/android_view_cts_InputQueueTest.cpp
new file mode 100644
index 0000000..769c95b
--- /dev/null
+++ b/tests/tests/view/jni/android_view_cts_InputQueueTest.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <android/input.h>
+#include <jni.h>
+#include <jniAssert.h>
+
+#include <array>
+#include <thread>
+
+#define LOG_TAG "InputQueueTest"
+
+bool waitForEvent(JNIEnv *env, jclass /* clazz */, jobject inputQueue) {
+ constexpr size_t NUM_TRIES = 5;
+ for (size_t i = 0; i < NUM_TRIES; i++) {
+ AInputQueue *nativeQueue = AInputQueue_fromJava(env, inputQueue);
+ if (nativeQueue != nullptr) {
+ int32_t numEvents = AInputQueue_hasEvents(nativeQueue);
+ if (numEvents > 0) {
+ return true;
+ }
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ }
+ return false;
+}
+
+void inputQueueTest(JNIEnv *env, jclass /* clazz */, jobject inputQueue) {
+ AInputQueue *nativeQueue = AInputQueue_fromJava(env, inputQueue);
+ ASSERT(nativeQueue != nullptr, "Native input queue not returned");
+ AInputEvent *event = nullptr;
+ ASSERT(AInputQueue_getEvent(nativeQueue, &event) >= 0, "getEvent did not succeed");
+ ASSERT(AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION, "Wrong event type");
+ ASSERT(AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN, "Wrong action");
+ AInputQueue_finishEvent(nativeQueue, event, true);
+}
+
+const std::array<JNINativeMethod, 2> JNI_METHODS = {{
+ {"waitForEvent", "(Landroid/view/InputQueue;)Z", (void *)waitForEvent},
+ {"inputQueueTest", "(Landroid/view/InputQueue;)V", (void *)inputQueueTest},
+}};
+
+jint register_android_view_cts_InputQueueTest(JNIEnv *env) {
+ jclass clazzTest = env->FindClass("android/view/cts/InputQueueTest");
+ return env->RegisterNatives(clazzTest, JNI_METHODS.data(), JNI_METHODS.size());
+}
diff --git a/tests/tests/view/res/layout/onbackinvokeddispatcher_dialog_layout.xml b/tests/tests/view/res/layout/onbackinvokeddispatcher_dialog_layout.xml
new file mode 100644
index 0000000..b8c365a
--- /dev/null
+++ b/tests/tests/view/res/layout/onbackinvokeddispatcher_dialog_layout.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <View
+ android:id="@+id/test_view_in_dialog"
+ android:layout_width="100px"
+ android:layout_height="100px"
+ android:layout_centerInParent="true"
+ android:background="#FFF"/>
+
+</RelativeLayout>
diff --git a/tests/tests/view/res/layout/onbackinvokeddispatcher_layout.xml b/tests/tests/view/res/layout/onbackinvokeddispatcher_layout.xml
new file mode 100644
index 0000000..40ff58b
--- /dev/null
+++ b/tests/tests/view/res/layout/onbackinvokeddispatcher_layout.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <View
+ android:id="@+id/test_view"
+ android:layout_width="100px"
+ android:layout_height="100px"
+ android:layout_centerInParent="true"
+ android:background="#FFF"/>
+
+</RelativeLayout>
diff --git a/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java b/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
index 1b120f9..72b4e97 100644
--- a/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
+++ b/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
@@ -18,6 +18,7 @@
import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
import static android.view.cts.surfacevalidator.ASurfaceControlTestActivity.MultiRectChecker;
+import static android.view.cts.surfacevalidator.ASurfaceControlTestActivity.WAIT_TIMEOUT_S;
import static android.view.cts.util.ASurfaceControlTestUtils.applyAndDeleteSurfaceTransaction;
import static android.view.cts.util.ASurfaceControlTestUtils.createSurfaceTransaction;
import static android.view.cts.util.ASurfaceControlTestUtils.nSurfaceControl_acquire;
@@ -30,6 +31,7 @@
import static android.view.cts.util.ASurfaceControlTestUtils.nSurfaceTransaction_releaseBuffer;
import static android.view.cts.util.ASurfaceControlTestUtils.nSurfaceTransaction_setDamageRegion;
import static android.view.cts.util.ASurfaceControlTestUtils.nSurfaceTransaction_setDesiredPresentTime;
+import static android.view.cts.util.ASurfaceControlTestUtils.nSurfaceTransaction_setFrameTimeline;
import static android.view.cts.util.ASurfaceControlTestUtils.nSurfaceTransaction_setOnCommitCallback;
import static android.view.cts.util.ASurfaceControlTestUtils.nSurfaceTransaction_setOnCommitCallbackWithoutContext;
import static android.view.cts.util.ASurfaceControlTestUtils.nSurfaceTransaction_setOnCompleteCallback;
@@ -48,14 +50,17 @@
import static android.view.cts.util.ASurfaceControlTestUtils.setScale;
import static android.view.cts.util.ASurfaceControlTestUtils.setVisibility;
import static android.view.cts.util.ASurfaceControlTestUtils.setZOrder;
+import static android.view.cts.util.FrameCallbackData.nGetFrameTimelines;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.SystemClock;
+import android.os.Trace;
import android.platform.test.annotations.RequiresDevice;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
@@ -65,11 +70,14 @@
import android.view.cts.surfacevalidator.ASurfaceControlTestActivity.PixelChecker;
import android.view.cts.surfacevalidator.PixelColor;
import android.view.cts.util.ASurfaceControlTestUtils;
+import android.view.cts.util.FrameCallbackData;
+import android.view.cts.util.FrameCallbackData.FrameTimeline;
import androidx.annotation.NonNull;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -238,7 +246,7 @@
private void verifyTest(BasicSurfaceHolderCallback callback, PixelChecker pixelChecker) {
SurfaceHolderCallback surfaceHolderCallback = new SurfaceHolderCallback(callback);
- mActivity.verifyTest(surfaceHolderCallback, pixelChecker);
+ mActivity.verifyTest(surfaceHolderCallback, pixelChecker, mName);
}
@Test
@@ -447,7 +455,7 @@
long surfaceControl = createFromWindow(holder.getSurface());
setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
- PixelColor.RED);
+ PixelColor.TRANSLUCENT_RED);
setBufferOpaque(surfaceControl, true);
}
},
@@ -460,7 +468,7 @@
}
@Test
- public void testSurfaceTransaction_setBufferOpaque_transparent() {
+ public void testSurfaceTransaction_setBufferOpaque_translucent() {
verifyTest(
new BasicSurfaceHolderCallback() {
@Override
@@ -468,7 +476,7 @@
long surfaceControl = createFromWindow(holder.getSurface());
setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
- PixelColor.TRANSPARENT_RED);
+ PixelColor.TRANSLUCENT_RED);
setBufferOpaque(surfaceControl, false);
}
},
@@ -669,7 +677,6 @@
setGeometry(surfaceControl2, 0, 0, 100, 100, 70, 20, 90, 50, 0);
}
},
-
new MultiRectChecker(DEFAULT_RECT) {
@Override
public PixelColor getExpectedColor(int x, int y) {
@@ -697,7 +704,6 @@
PixelColor.MAGENTA, PixelColor.GREEN);
}
},
-
new MultiRectChecker(DEFAULT_RECT) {
@Override
public PixelColor getExpectedColor(int x, int y) {
@@ -1619,6 +1625,50 @@
}
@Test
+ public void testSurfaceTransaction_setPositionAndScale() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ long surfaceControl = createFromWindow(holder.getSurface());
+
+ setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+ DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+ PixelColor.MAGENTA, PixelColor.GREEN);
+
+ // Set the position to -50, -50 in parent space then scale 2x in each
+ // direction relative to 0,0. The end result should be a -50,-50,150,150
+ // buffer coverage or essentially a 2x center-scale
+
+ setPosition(surfaceControl, -50, -50);
+ setScale(surfaceControl, 2, 2);
+ }
+ },
+ new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+ @Override
+ public PixelColor getExpectedColor(int x, int y) {
+ int halfWidth = DEFAULT_LAYOUT_WIDTH / 2;
+ int halfHeight = DEFAULT_LAYOUT_HEIGHT / 2;
+ if (x < halfWidth && y < halfHeight) {
+ return RED;
+ } else if (x >= halfWidth && y < halfHeight) {
+ return BLUE;
+ } else if (x < halfWidth && y >= halfHeight) {
+ return GREEN;
+ } else {
+ return MAGENTA;
+ }
+ }
+
+ @Override
+ public boolean checkPixels(int matchingPixelCount, int width, int height) {
+ // There will be sampling artifacts along the center line, ignore those
+ return matchingPixelCount > 9000 && matchingPixelCount < 11000;
+ }
+ });
+ }
+
+ @Test
public void testSurfaceTransaction_setBufferTransform90() {
verifyTest(
new BasicSurfaceHolderCallback() {
@@ -1764,6 +1814,147 @@
});
}
+ private void verifySetFrameTimeline(boolean mUsePreferredIndex, SurfaceHolder holder) {
+ TimedTransactionListener onCompleteCallback = new TimedTransactionListener();
+ long surfaceControl = nSurfaceControl_createFromWindow(holder.getSurface());
+ assertTrue("failed to create surface control", surfaceControl != 0);
+ long surfaceTransaction = createSurfaceTransaction();
+ long buffer = nSurfaceTransaction_setSolidBuffer(surfaceControl, surfaceTransaction,
+ DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+ assertTrue("failed to set buffer", buffer != 0);
+
+ // Get choreographer frame timelines.
+ FrameCallbackData frameCallbackData = nGetFrameTimelines();
+ FrameTimeline[] frameTimelines = frameCallbackData.getFrameTimelines();
+ assertTrue("Frame timelines length needs to be greater than 1", frameTimelines
+ .length > 1);
+ long interval = frameTimelines[1].getDeadline() - frameTimelines[0].getDeadline();
+
+ int timelineIndex = frameCallbackData.getPreferredFrameTimelineIndex();
+ if (!mUsePreferredIndex) {
+ assertNotEquals("Preferred frame timeline index should not be last index",
+ frameTimelines.length - 1,
+ frameCallbackData.getPreferredFrameTimelineIndex());
+ timelineIndex = frameTimelines.length - 1;
+ }
+ long vsyncId = frameTimelines[timelineIndex].getVsyncId();
+ Trace.beginSection("Surface transaction created " + vsyncId);
+ nSurfaceTransaction_setFrameTimeline(surfaceTransaction, vsyncId);
+ nSurfaceTransaction_setOnCompleteCallback(surfaceTransaction,
+ true /* waitForFence */, onCompleteCallback);
+ applyAndDeleteSurfaceTransaction(surfaceTransaction);
+ Trace.endSection();
+
+ Trace.beginSection("Wait for complete callback " + vsyncId);
+ // Wait for callbacks to fire.
+ try {
+ onCompleteCallback.mLatch.await(1, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ }
+ if (onCompleteCallback.mLatch.getCount() > 0) {
+ Log.e(TAG, "Failed to wait for callback");
+ }
+ Trace.endSection();
+
+ assertEquals(0, onCompleteCallback.mLatch.getCount());
+ assertTrue(onCompleteCallback.mCallbackTime > 0);
+ assertTrue(onCompleteCallback.mLatchTime > 0);
+
+ FrameTimeline frameTimeline = frameTimelines[timelineIndex];
+ long lowerThreshold = frameTimeline.getExpectedPresentTime() - interval / 2;
+ assertTrue("Presented too early using frame timeline index=" + timelineIndex
+ + " (preferred index="
+ + frameCallbackData.getPreferredFrameTimelineIndex()
+ + "), vsyncId=" + frameTimeline.getVsyncId() + ", presentTime="
+ + onCompleteCallback.mPresentTime + ", expectedPresentTime="
+ + frameTimeline.getExpectedPresentTime()
+ + ", diff (ns)="
+ + (frameTimeline.getExpectedPresentTime()
+ - onCompleteCallback.mPresentTime),
+ frameTimeline.getExpectedPresentTime() >= lowerThreshold);
+ long upperThreshold = frameTimeline.getExpectedPresentTime() + interval / 2;
+ assertTrue("Present too late using frame timeline index=" + timelineIndex
+ + " (preferred index="
+ + frameCallbackData.getPreferredFrameTimelineIndex()
+ + "), vsyncId=" + frameTimeline.getVsyncId() + ", presentTime="
+ + onCompleteCallback.mPresentTime + ", expectedPresentTime="
+ + frameTimeline.getExpectedPresentTime()
+ + ", diff (ns)="
+ + (frameTimeline.getExpectedPresentTime()
+ - onCompleteCallback.mPresentTime),
+ frameTimeline.getExpectedPresentTime() < upperThreshold);
+ }
+
+ @Test
+ @RequiresDevice // emulators can't support sync fences
+ public void testSurfaceTransaction_setFrameTimeline_preferredIndex() {
+ Trace.beginSection(
+ "testSurfaceTransaction_setFrameTimeline_preferredIndex");
+ Trace.endSection();
+
+ BasicSurfaceHolderCallback basicSurfaceHolderCallback = new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder surfaceHolder) {
+ // Noop.
+ }
+ };
+ final CountDownLatch readyFence = new CountDownLatch(1);
+ ASurfaceControlTestActivity.SurfaceHolderCallback surfaceHolderCallback =
+ new ASurfaceControlTestActivity.SurfaceHolderCallback(
+ new SurfaceHolderCallback(basicSurfaceHolderCallback), readyFence,
+ mActivity.getParentFrameLayout().getViewTreeObserver());
+ mActivity.createSurface(surfaceHolderCallback);
+ try {
+ assertTrue("timeout", readyFence.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ } catch (InterruptedException e) {
+ Assert.fail("interrupted");
+ }
+ verifySetFrameTimeline(true, mActivity.getSurfaceView().getHolder());
+ mActivity.verifyScreenshot(
+ new PixelChecker(PixelColor.RED) { //10000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 9000 && pixelCount < 11000;
+ }
+ }, mName);
+
+ }
+
+ @Test
+ @RequiresDevice // emulators can't support sync fences
+ public void testSurfaceTransaction_setFrameTimeline_notPreferredIndex() {
+ Trace.beginSection(
+ "testSurfaceTransaction_setFrameTimeline_notPreferredIndex");
+ Trace.endSection();
+
+ BasicSurfaceHolderCallback basicSurfaceHolderCallback = new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder surfaceHolder) {
+ // Noop.
+ }
+ };
+ final CountDownLatch readyFence = new CountDownLatch(1);
+ ASurfaceControlTestActivity.SurfaceHolderCallback surfaceHolderCallback =
+ new ASurfaceControlTestActivity.SurfaceHolderCallback(
+ new SurfaceHolderCallback(basicSurfaceHolderCallback), readyFence,
+ mActivity.getParentFrameLayout().getViewTreeObserver());
+ mActivity.createSurface(surfaceHolderCallback);
+ try {
+ assertTrue("timeout", readyFence.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ } catch (InterruptedException e) {
+ Assert.fail("interrupted");
+ }
+ verifySetFrameTimeline(false, mActivity.getSurfaceView().getHolder());
+ mActivity.verifyScreenshot(
+ new PixelChecker(PixelColor.RED) { //10000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 9000 && pixelCount < 11000;
+ }
+ }, mName);
+
+ }
+
static class TimedTransactionListener implements
ASurfaceControlTestUtils.TransactionCompleteListener {
long mCallbackTime = -1;
diff --git a/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java b/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java
index 96aa43b..261895d 100644
--- a/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java
+++ b/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java
@@ -75,6 +75,14 @@
private static native void nativeTestPostCallback64WithoutDelayEventuallyRunsCallbacks(
long ptr);
private static native void nativeTestPostCallback64WithDelayEventuallyRunsCallbacks(long ptr);
+ private static native void nativeTestPostExtendedCallbackWithoutDelayEventuallyRunsCallbacks(
+ long ptr);
+ private static native void nativeTestFrameCallbackDataVsyncIdValid(
+ long ptr);
+ private static native void nativeTestFrameCallbackDataDeadlineInFuture(
+ long ptr);
+ private static native void nativeTestFrameCallbackDataExpectedPresentTimeInFuture(
+ long ptr);
private static native void nativeTestPostCallbackMixedWithoutDelayEventuallyRunsCallbacks(
long ptr);
private static native void nativeTestPostCallbackMixedWithDelayEventuallyRunsCallbacks(
@@ -122,6 +130,30 @@
@MediumTest
@Test
+ public void testPostExtendedCallbackWithoutDelayEventuallyRunsCallbacks() {
+ nativeTestPostExtendedCallbackWithoutDelayEventuallyRunsCallbacks(mChoreographerPtr);
+ }
+
+ @MediumTest
+ @Test
+ public void testFrameCallbackDataVsyncIdValid() {
+ nativeTestFrameCallbackDataVsyncIdValid(mChoreographerPtr);
+ }
+
+ @MediumTest
+ @Test
+ public void testFrameCallbackDataDeadlineInFuture() {
+ nativeTestFrameCallbackDataDeadlineInFuture(mChoreographerPtr);
+ }
+
+ @MediumTest
+ @Test
+ public void testFrameCallbackDataExpectedPresentTimeInFuture() {
+ nativeTestFrameCallbackDataExpectedPresentTimeInFuture(mChoreographerPtr);
+ }
+
+ @MediumTest
+ @Test
public void testPostCallback64WithoutDelayEventuallyRunsCallbacks() {
nativeTestPostCallback64WithoutDelayEventuallyRunsCallbacks(mChoreographerPtr);
}
diff --git a/tests/tests/view/src/android/view/cts/ChoreographerTest.java b/tests/tests/view/src/android/view/cts/ChoreographerTest.java
index 3760b73..f6c9d4d 100644
--- a/tests/tests/view/src/android/view/cts/ChoreographerTest.java
+++ b/tests/tests/view/src/android/view/cts/ChoreographerTest.java
@@ -37,6 +37,8 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import java.util.HashSet;
+
@MediumTest
@RunWith(AndroidJUnit4.class)
public class ChoreographerTest {
@@ -290,4 +292,138 @@
public void testRemoveNullFrameCallback() {
mChoreographer.removeFrameCallback(null);
}
+
+ @Test
+ public void testPostExtendedFrameCallbackWithoutDelay() {
+ final Choreographer.ExtendedFrameCallback addedCallback1 = mock(
+ Choreographer.ExtendedFrameCallback.class);
+ final Choreographer.ExtendedFrameCallback addedCallback2 = mock(
+ Choreographer.ExtendedFrameCallback.class);
+ final Choreographer.ExtendedFrameCallback removedCallback = mock(
+ Choreographer.ExtendedFrameCallback.class);
+
+ // Add and remove a few callbacks.
+ long postTimeNanos = System.nanoTime();
+ mChoreographer.postExtendedFrameCallback(addedCallback1);
+ mChoreographer.postExtendedFrameCallback(addedCallback2);
+ mChoreographer.postExtendedFrameCallback(removedCallback);
+ mChoreographer.removeExtendedFrameCallback(removedCallback);
+
+ // We expect the remaining callbacks to have been invoked once.
+ ArgumentCaptor<Choreographer.FrameData> captor1 = ArgumentCaptor.forClass(
+ Choreographer.FrameData.class);
+ ArgumentCaptor<Choreographer.FrameData> captor2 = ArgumentCaptor.forClass(
+ Choreographer.FrameData.class);
+ verify(addedCallback1, timeout(NOMINAL_VSYNC_PERIOD * 10).times(1)).onVsync(
+ captor1.capture());
+ verify(addedCallback2, times(1)).onVsync(captor2.capture());
+ verifyZeroInteractions(removedCallback);
+ assertTimeDeltaLessThan(captor1.getValue().getFrameTimeNanos() - postTimeNanos,
+ NOMINAL_VSYNC_PERIOD * 10 * NANOS_PER_MS);
+ assertTimeDeltaLessThan(captor2.getValue().getFrameTimeNanos() - postTimeNanos,
+ NOMINAL_VSYNC_PERIOD * 10 * NANOS_PER_MS);
+
+ // If we post a callback again, then it should be invoked again.
+ postTimeNanos = System.nanoTime();
+ mChoreographer.postExtendedFrameCallback(addedCallback1);
+
+ verify(addedCallback1, timeout(NOMINAL_VSYNC_PERIOD * 10).times(2)).onVsync(
+ captor1.capture());
+ verify(addedCallback2, times(1)).onVsync(captor2.capture());
+ verifyZeroInteractions(removedCallback);
+ assertTimeDeltaLessThan(captor1.getValue().getFrameTimeNanos() - postTimeNanos,
+ NOMINAL_VSYNC_PERIOD * 10 * NANOS_PER_MS);
+ // Callback #2 is not invoked a second time so the time delta is not checked here.
+ }
+
+ @Test
+ public void testPostExtendedFrameCallbackFrameDataVsyncIdValid() {
+ final Choreographer.ExtendedFrameCallback addedCallback = mock(
+ Choreographer.ExtendedFrameCallback.class);
+ long postTimeNanos = System.nanoTime();
+ mChoreographer.postExtendedFrameCallback(addedCallback);
+
+ ArgumentCaptor<Choreographer.FrameData> captor = ArgumentCaptor.forClass(
+ Choreographer.FrameData.class);
+ verify(addedCallback, timeout(NOMINAL_VSYNC_PERIOD * 10).times(1)).onVsync(
+ captor.capture());
+
+ Choreographer.FrameData frameData = captor.getValue();
+ assertTrue("Number of frame timelines should be greater than 0",
+ frameData.getFrameTimelines().length > 0);
+ HashSet<Long> pastVsyncIds = new HashSet();
+ for (Choreographer.FrameTimeline frameTimeline : frameData.getFrameTimelines()) {
+ long vsyncId = frameTimeline.getVsyncId();
+ assertTrue("Invalid vsync ID", vsyncId > 0);
+ assertTrue("Vsync ID should be unique", !pastVsyncIds.contains(vsyncId));
+ pastVsyncIds.add(vsyncId);
+ }
+ }
+
+ @Test
+ public void testPostExtendedFrameCallbackFrameDataDeadlineInFuture() {
+ final Choreographer.ExtendedFrameCallback addedCallback = mock(
+ Choreographer.ExtendedFrameCallback.class);
+ long postTimeNanos = System.nanoTime();
+ mChoreographer.postExtendedFrameCallback(addedCallback);
+
+ ArgumentCaptor<Choreographer.FrameData> captor = ArgumentCaptor.forClass(
+ Choreographer.FrameData.class);
+ verify(addedCallback, timeout(NOMINAL_VSYNC_PERIOD * 10).times(1)).onVsync(
+ captor.capture());
+
+ Choreographer.FrameData frameData = captor.getValue();
+ assertTrue("Number of frame timelines should be greater than 0",
+ frameData.getFrameTimelines().length > 0);
+ long lastValue = frameData.getFrameTimeNanos();
+ for (Choreographer.FrameTimeline frameTimeline : frameData.getFrameTimelines()) {
+ long deadline = frameTimeline.getDeadlineNanos();
+ assertTrue("Deadline must be after start time", deadline > postTimeNanos);
+ assertTrue("Deadline must be after frame time",
+ deadline > frameData.getFrameTimeNanos());
+ assertTrue("Deadline must be after the previous frame deadline",
+ deadline > lastValue);
+ lastValue = deadline;
+ }
+ }
+
+ @Test
+ public void testPostExtendedFrameCallbackFrameDataExpectedPresentTimeInFuture() {
+ final Choreographer.ExtendedFrameCallback addedCallback = mock(
+ Choreographer.ExtendedFrameCallback.class);
+ long postTimeNanos = System.nanoTime();
+ mChoreographer.postExtendedFrameCallback(addedCallback);
+
+ ArgumentCaptor<Choreographer.FrameData> captor = ArgumentCaptor.forClass(
+ Choreographer.FrameData.class);
+ verify(addedCallback, timeout(NOMINAL_VSYNC_PERIOD * 10).times(1)).onVsync(
+ captor.capture());
+
+ Choreographer.FrameData frameData = captor.getValue();
+ assertTrue("Number of frame timelines should be greater than 0",
+ frameData.getFrameTimelines().length > 0);
+ long lastValue = frameData.getFrameTimeNanos();
+ for (Choreographer.FrameTimeline frameTimeline : frameData.getFrameTimelines()) {
+ long expectedPresentTime = frameTimeline.getExpectedPresentTimeNanos();
+ assertTrue("Expected present time must be after start time",
+ expectedPresentTime > postTimeNanos);
+ assertTrue("Expected present time must be after frame time",
+ expectedPresentTime > frameData.getFrameTimeNanos());
+ assertTrue(
+ "Expected present time must be after the previous frame expected present "
+ + "time",
+ expectedPresentTime > lastValue);
+ lastValue = expectedPresentTime;
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPostNullExtendedFrameCallback() {
+ mChoreographer.postExtendedFrameCallback(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testRemoveNullExtendedFrameCallback() {
+ mChoreographer.removeExtendedFrameCallback(null);
+ }
}
diff --git a/tests/tests/view/src/android/view/cts/input/InputEventInterceptTestActivity.java b/tests/tests/view/src/android/view/cts/InputEventInterceptTestActivity.java
similarity index 100%
rename from tests/tests/view/src/android/view/cts/input/InputEventInterceptTestActivity.java
rename to tests/tests/view/src/android/view/cts/InputEventInterceptTestActivity.java
diff --git a/tests/tests/view/src/android/view/cts/InputQueueCtsActivity.java b/tests/tests/view/src/android/view/cts/InputQueueCtsActivity.java
new file mode 100644
index 0000000..2705f53
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/InputQueueCtsActivity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.os.Bundle;
+import android.view.InputQueue;
+
+public class InputQueueCtsActivity extends Activity implements InputQueue.Callback {
+ private InputQueue mInputQueue;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getWindow().takeInputQueue(this);
+ }
+
+ @Override
+ public void onInputQueueCreated(InputQueue inputQueue) {
+ mInputQueue = inputQueue;
+ }
+
+ @Override
+ public void onInputQueueDestroyed(InputQueue inputQueue) {}
+
+ public InputQueue getInputQueue() {
+ return mInputQueue;
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/InputQueueTest.java b/tests/tests/view/src/android/view/cts/InputQueueTest.java
new file mode 100644
index 0000000..aefa38c
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/InputQueueTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.assertTrue;
+
+import android.app.Instrumentation;
+import android.os.SystemClock;
+import android.view.InputQueue;
+import android.view.MotionEvent;
+import android.view.Window;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test {@link AInputQueue}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputQueueTest {
+ private static final String LOG_TAG = InputQueueTest.class.getSimpleName();
+ static {
+ System.loadLibrary("ctsview_jni");
+ }
+
+ private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+ private static native boolean waitForEvent(InputQueue inputQueue);
+ private static native void inputQueueTest(InputQueue inputQueue);
+
+ @Rule
+ public ActivityTestRule<InputQueueCtsActivity> mTestActivityRule =
+ new ActivityTestRule<>(InputQueueCtsActivity.class);
+
+ @Test
+ public void testNativeInputQueue() throws Throwable {
+ InputQueueCtsActivity activity = mTestActivityRule.getActivity();
+ Window window = activity.getWindow();
+ InputQueue inputQueue = activity.getInputQueue();
+
+ // An event is created Java-side.
+ long now = SystemClock.uptimeMillis();
+ MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ window.injectInputEvent(event);
+
+ assertTrue("Timed out waiting for event", waitForEvent(inputQueue));
+
+ inputQueueTest(inputQueue); // Check the injected event is received on the native side.
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/MotionEventTest.java b/tests/tests/view/src/android/view/cts/MotionEventTest.java
index 92e5832..bb8c20f 100644
--- a/tests/tests/view/src/android/view/cts/MotionEventTest.java
+++ b/tests/tests/view/src/android/view/cts/MotionEventTest.java
@@ -169,10 +169,10 @@
public void testObtainFromPropertyArrays() {
PointerCoordsBuilder coordsBuilder0 =
withCoords(X_3F, Y_4F).withPressure(PRESSURE_1F).withSize(SIZE_1F).
- withTool(1.2f, 1.4f);
+ withTool(1.2f, 1.4f).withGenericAxis1(2.6f);
PointerCoordsBuilder coordsBuilder1 =
withCoords(X_3F + 1.0f, Y_4F - 2.0f).withPressure(PRESSURE_1F + 0.2f).
- withSize(SIZE_1F + 0.5f).withTouch(2.2f, 0.6f);
+ withSize(SIZE_1F + 0.5f).withTouch(2.2f, 0.6f).withGenericAxis1(2.6f);
PointerPropertiesBuilder propertiesBuilder0 =
withProperties(0, MotionEvent.TOOL_TYPE_FINGER);
@@ -451,9 +451,11 @@
@Test
public void testGetCurrentDataWithTwoPointers() {
PointerCoordsBuilder coordsBuilder0 =
- withCoords(10.0f, 20.0f).withPressure(1.2f).withSize(2.0f).withTool(1.2f, 1.4f);
+ withCoords(10.0f, 20.0f).withPressure(1.2f).withSize(2.0f).withTool(1.2f,
+ 1.4f).withGenericAxis1(4.4f);
PointerCoordsBuilder coordsBuilder1 =
- withCoords(30.0f, 40.0f).withPressure(1.4f).withSize(3.0f).withTouch(2.2f, 0.6f);
+ withCoords(30.0f, 40.0f).withPressure(1.4f).withSize(3.0f).withTouch(2.2f,
+ 0.6f).withGenericAxis1(6.6f);
PointerPropertiesBuilder propertiesBuilder0 =
withProperties(0, MotionEvent.TOOL_TYPE_FINGER);
@@ -484,9 +486,11 @@
@Test
public void testGetRawCoordsWithTwoPointers() {
PointerCoordsBuilder coordsBuilder0 =
- withCoords(10.0f, 20.0f).withPressure(1.2f).withSize(2.0f).withTool(1.2f, 1.4f);
+ withCoords(10.0f, 20.0f).withPressure(1.2f).withSize(2.0f).withTool(1.2f,
+ 1.4f).withGenericAxis1(4.4f);
PointerCoordsBuilder coordsBuilder1 =
- withCoords(30.0f, 40.0f).withPressure(1.4f).withSize(3.0f).withTouch(2.2f, 0.6f);
+ withCoords(30.0f, 40.0f).withPressure(1.4f).withSize(3.0f).withTouch(2.2f,
+ 0.6f).withGenericAxis1(6.6f);
PointerPropertiesBuilder propertiesBuilder0 =
withProperties(0, MotionEvent.TOOL_TYPE_FINGER);
@@ -518,10 +522,10 @@
// PHASE 1 - construct the initial data for the event
PointerCoordsBuilder coordsBuilderInitial0 =
withCoords(10.0f, 20.0f).withPressure(1.2f).withSize(2.0f).withTool(1.2f, 1.4f).
- withTouch(0.7f, 0.6f).withOrientation(2.0f);
+ withTouch(0.7f, 0.6f).withOrientation(2.0f).withGenericAxis1(4.4f);
PointerCoordsBuilder coordsBuilderInitial1 =
withCoords(30.0f, 40.0f).withPressure(1.4f).withSize(3.0f).withTool(1.3f, 1.7f).
- withTouch(2.7f, 3.6f).withOrientation(1.0f);
+ withTouch(2.7f, 3.6f).withOrientation(1.0f).withGenericAxis1(5.4f);
PointerPropertiesBuilder propertiesBuilder0 =
withProperties(0, MotionEvent.TOOL_TYPE_FINGER);
@@ -547,10 +551,10 @@
// PHASE 2 - add a new batch of data to our event
PointerCoordsBuilder coordsBuilderNext0 =
withCoords(15.0f, 25.0f).withPressure(1.6f).withSize(2.2f).withTool(1.2f, 1.4f).
- withTouch(1.0f, 0.9f).withOrientation(2.2f);
+ withTouch(1.0f, 0.9f).withOrientation(2.2f).withGenericAxis1(7.4f);
PointerCoordsBuilder coordsBuilderNext1 =
withCoords(35.0f, 45.0f).withPressure(1.8f).withSize(3.2f).withTool(1.2f, 1.4f).
- withTouch(0.7f, 0.6f).withOrientation(2.9f);
+ withTouch(0.7f, 0.6f).withOrientation(2.9f).withGenericAxis1(8.4f);
mMotionEventDynamic.addBatch(mEventTime + 10,
new PointerCoords[] { coordsBuilderNext0.build(), coordsBuilderNext1.build() }, 0);
@@ -576,10 +580,10 @@
// PHASE 3 - add one more new batch of data to our event
PointerCoordsBuilder coordsBuilderLast0 =
withCoords(18.0f, 28.0f).withPressure(1.1f).withSize(2.9f).withTool(1.5f, 1.9f).
- withTouch(1.2f, 5.0f).withOrientation(3.2f);
+ withTouch(1.2f, 5.0f).withOrientation(3.1f).withGenericAxis1(1.4f);
PointerCoordsBuilder coordsBuilderLast1 =
withCoords(38.0f, 48.0f).withPressure(1.2f).withSize(2.5f).withTool(0.2f, 0.4f).
- withTouch(2.7f, 4.6f).withOrientation(0.2f);
+ withTouch(2.7f, 4.6f).withOrientation(0.2f).withGenericAxis1(5.4f);
mMotionEventDynamic.addBatch(mEventTime + 20,
new PointerCoords[] { coordsBuilderLast0.build(), coordsBuilderLast1.build() }, 0);
@@ -676,7 +680,8 @@
originalRawCoords[i] = new PointerCoords(c);
}
final MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
- pointerCount, pointerIds, pointerCoords, 0, 0, 0, 0, 0, 0, 0);
+ pointerCount, pointerIds, pointerCoords, 0, 0, 0, 0, 0,
+ InputDevice.SOURCE_TOUCHSCREEN, 0);
dump("Original points.", event);
// Check original raw X and Y assumption.
diff --git a/tests/tests/view/src/android/view/cts/MotionEventUtils.java b/tests/tests/view/src/android/view/cts/MotionEventUtils.java
index 74291c7..3747a46 100644
--- a/tests/tests/view/src/android/view/cts/MotionEventUtils.java
+++ b/tests/tests/view/src/android/view/cts/MotionEventUtils.java
@@ -84,6 +84,7 @@
private float toolMajor;
private float toolMinor;
private float orientation;
+ private float generic1;
public PointerCoordsBuilder withPressure(float pressure) {
this.pressure = pressure;
@@ -112,6 +113,11 @@
return this;
}
+ public PointerCoordsBuilder withGenericAxis1(float generic1) {
+ this.generic1 = generic1;
+ return this;
+ }
+
public PointerCoords build() {
final PointerCoords pointerCoords = new PointerCoords();
pointerCoords.x = x;
@@ -123,6 +129,7 @@
pointerCoords.toolMajor = toolMajor;
pointerCoords.toolMinor = toolMinor;
pointerCoords.orientation = orientation;
+ pointerCoords.setAxisValue(MotionEvent.AXIS_GENERIC_1, generic1);
return pointerCoords;
}
@@ -167,6 +174,9 @@
that.getOrientation(), this.orientation, DELTA);
assertEquals("Orientation should be the same",
that.getAxisValue(MotionEvent.AXIS_ORIENTATION), this.orientation, DELTA);
+
+ assertEquals("Generic axis 1 should be the same",
+ that.getAxisValue(MotionEvent.AXIS_GENERIC_1), this.generic1, DELTA);
}
public void verifyMatches(MotionEvent that, int pointerIndex) {
@@ -220,6 +230,10 @@
assertEquals("Orientation should be the same",
that.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex), this.orientation,
DELTA);
+
+ assertEquals("Generic axis 1 should be the same",
+ that.getAxisValue(MotionEvent.AXIS_GENERIC_1, pointerIndex), this.generic1,
+ DELTA);
}
public void verifyMatchesHistorical(MotionEvent that, int position) {
@@ -273,6 +287,10 @@
assertEquals("Orientation should be the same",
that.getHistoricalAxisValue(MotionEvent.AXIS_ORIENTATION, position),
this.orientation, DELTA);
+
+ assertEquals("Generic axis 1 should be the same",
+ that.getHistoricalAxisValue(MotionEvent.AXIS_GENERIC_1, position),
+ this.generic1, DELTA);
}
public void verifyMatchesHistorical(MotionEvent that, int pointerIndex, int position) {
@@ -334,6 +352,10 @@
that.getHistoricalAxisValue(MotionEvent.AXIS_ORIENTATION,
pointerIndex, position),
this.orientation, DELTA);
+
+ assertEquals("Generic axis 1 should be the same",
+ that.getHistoricalAxisValue(MotionEvent.AXIS_GENERIC_1, pointerIndex, position),
+ this.generic1, DELTA);
}
public void verifyMatchesPointerCoords(PointerCoords that) {
@@ -373,6 +395,9 @@
that.orientation, this.orientation, DELTA);
assertEquals("Orientation should be the same",
that.getAxisValue(MotionEvent.AXIS_ORIENTATION), this.orientation, DELTA);
+
+ assertEquals("Generic axis 1 should be the same",
+ that.getAxisValue(MotionEvent.AXIS_GENERIC_1), this.generic1, DELTA);
}
public void verifyMatchesPointerCoords(MotionEvent motionEvent, int pointerIndex) {
diff --git a/tests/tests/view/src/android/view/cts/MotionEvent_PointerCoordsTest.java b/tests/tests/view/src/android/view/cts/MotionEvent_PointerCoordsTest.java
index e39f1cb..6ed2d34 100644
--- a/tests/tests/view/src/android/view/cts/MotionEvent_PointerCoordsTest.java
+++ b/tests/tests/view/src/android/view/cts/MotionEvent_PointerCoordsTest.java
@@ -40,7 +40,7 @@
@Before
public void setup() {
mBuilder = withCoords(10.0f, 20.0f).withPressure(1.2f).withSize(2.0f).withTool(1.2f, 1.4f).
- withTouch(3.0f, 2.4f);
+ withTouch(3.0f, 2.4f).withGenericAxis1(4.4f);
mPointerCoords = mBuilder.build();
}
@@ -54,42 +54,56 @@
// Change value of X
mPointerCoords.setAxisValue(MotionEvent.AXIS_X, 15.0f);
withCoords(15.0f, 20.0f).withPressure(1.2f).withSize(2.0f).withTool(1.2f, 1.4f).
- withTouch(3.0f, 2.4f).verifyMatchesPointerCoords(mPointerCoords);
+ withTouch(3.0f, 2.4f).withGenericAxis1(4.4f).verifyMatchesPointerCoords(
+ mPointerCoords);
// Change value of Y
mPointerCoords.setAxisValue(MotionEvent.AXIS_Y, 25.0f);
withCoords(15.0f, 25.0f).withPressure(1.2f).withSize(2.0f).withTool(1.2f, 1.4f).
- withTouch(3.0f, 2.4f).verifyMatchesPointerCoords(mPointerCoords);
+ withTouch(3.0f, 2.4f).withGenericAxis1(4.4f).verifyMatchesPointerCoords(
+ mPointerCoords);
// Change value of pressure
mPointerCoords.setAxisValue(MotionEvent.AXIS_PRESSURE, 2.2f);
withCoords(15.0f, 25.0f).withPressure(2.2f).withSize(2.0f).withTool(1.2f, 1.4f).
- withTouch(3.0f, 2.4f).verifyMatchesPointerCoords(mPointerCoords);
+ withTouch(3.0f, 2.4f).withGenericAxis1(4.4f).verifyMatchesPointerCoords(
+ mPointerCoords);
// Change value of size
mPointerCoords.setAxisValue(MotionEvent.AXIS_SIZE, 10.0f);
withCoords(15.0f, 25.0f).withPressure(2.2f).withSize(10.0f).withTool(1.2f, 1.4f).
- withTouch(3.0f, 2.4f).verifyMatchesPointerCoords(mPointerCoords);
+ withTouch(3.0f, 2.4f).withGenericAxis1(4.4f).verifyMatchesPointerCoords(
+ mPointerCoords);
// Change value of tool major
mPointerCoords.setAxisValue(MotionEvent.AXIS_TOOL_MAJOR, 7.0f);
withCoords(15.0f, 25.0f).withPressure(2.2f).withSize(10.0f).withTool(7.0f, 1.4f).
- withTouch(3.0f, 2.4f).verifyMatchesPointerCoords(mPointerCoords);
+ withTouch(3.0f, 2.4f).withGenericAxis1(4.4f).verifyMatchesPointerCoords(
+ mPointerCoords);
// Change value of tool minor
mPointerCoords.setAxisValue(MotionEvent.AXIS_TOOL_MINOR, 2.0f);
withCoords(15.0f, 25.0f).withPressure(2.2f).withSize(10.0f).withTool(7.0f, 2.0f).
- withTouch(3.0f, 2.4f).verifyMatchesPointerCoords(mPointerCoords);
+ withTouch(3.0f, 2.4f).withGenericAxis1(4.4f).verifyMatchesPointerCoords(
+ mPointerCoords);
// Change value of tool major
mPointerCoords.setAxisValue(MotionEvent.AXIS_TOUCH_MAJOR, 5.0f);
withCoords(15.0f, 25.0f).withPressure(2.2f).withSize(10.0f).withTool(7.0f, 2.0f).
- withTouch(5.0f, 2.4f).verifyMatchesPointerCoords(mPointerCoords);
+ withTouch(5.0f, 2.4f).withGenericAxis1(4.4f).verifyMatchesPointerCoords(
+ mPointerCoords);
// Change value of tool minor
mPointerCoords.setAxisValue(MotionEvent.AXIS_TOUCH_MINOR, 2.1f);
withCoords(15.0f, 25.0f).withPressure(2.2f).withSize(10.0f).withTool(7.0f, 2.0f).
- withTouch(5.0f, 2.1f).verifyMatchesPointerCoords(mPointerCoords);
+ withTouch(5.0f, 2.1f).withGenericAxis1(4.4f).verifyMatchesPointerCoords(
+ mPointerCoords);
+
+ // Change value of axis generic 1
+ mPointerCoords.setAxisValue(MotionEvent.AXIS_GENERIC_1, 4.6f);
+ withCoords(15.0f, 25.0f).withPressure(2.2f).withSize(10.0f).withTool(7.0f, 2.0f)
+ .withTouch(5.0f, 2.1f).withGenericAxis1(4.6f).verifyMatchesPointerCoords(
+ mPointerCoords);
}
@Test
@@ -103,6 +117,7 @@
public void testClear() {
mPointerCoords.clear();
withCoords(0.0f, 0.0f).withPressure(0.0f).withSize(0.0f).withTool(0.0f, 0.0f).
- withTouch(0.0f, 0.0f).verifyMatchesPointerCoords(mPointerCoords);
+ withTouch(0.0f, 0.0f).withGenericAxis1(0.0f).verifyMatchesPointerCoords(
+ mPointerCoords);
}
}
diff --git a/tests/tests/view/src/android/view/cts/OnBackInvokedDispatcherTest.java b/tests/tests/view/src/android/view/cts/OnBackInvokedDispatcherTest.java
new file mode 100644
index 0000000..841ffe9
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/OnBackInvokedDispatcherTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Dialog;
+import android.view.OnBackInvokedCallback;
+import android.view.OnBackInvokedDispatcher;
+import android.view.View;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test {@link OnBackInvokedDispatcher}.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class OnBackInvokedDispatcherTest {
+ private View mView;
+ private OnBackInvokedDispatcherTestActivity mActivity;
+ private Dialog mDialog;
+ private View mViewInDialog;
+
+ @Rule
+ public ActivityTestRule<OnBackInvokedDispatcherTestActivity> mActivityRule =
+ new ActivityTestRule<>(OnBackInvokedDispatcherTestActivity.class);
+
+ @Before
+ public void setUp() {
+ mActivity = mActivityRule.getActivity();
+ mView = mActivity.findViewById(R.id.test_view);
+ mDialog = mActivity.getDialog();
+ mViewInDialog = mDialog.findViewById(R.id.test_view_in_dialog);
+ }
+
+ @Test
+ public void testGetDispatcherOnView() {
+ OnBackInvokedDispatcher viewDispatcher = mView.getOnBackInvokedDispatcher();
+ assertEquals(viewDispatcher, mActivity.getOnBackInvokedDispatcher());
+ assertNotNull("OnBackInvokedDispatcher on View should not be null", viewDispatcher);
+ }
+
+ @Test
+ public void testGetDispatcherOnDialog() {
+ OnBackInvokedDispatcher dialogDispatcher = mDialog.getOnBackInvokedDispatcher();
+ OnBackInvokedDispatcher dialogViewDispatcher = mViewInDialog.getOnBackInvokedDispatcher();
+ assertEquals(dialogDispatcher, dialogViewDispatcher);
+ assertNotNull("OnBackInvokedDispatcher on Dialog should not be null", dialogDispatcher);
+ }
+
+ @Test
+ public void testRegisterAndUnregisterCallbacks() {
+ OnBackInvokedDispatcher dispatcher = mView.getOnBackInvokedDispatcher();
+ OnBackInvokedCallback callback1 = createBackCallback();
+ OnBackInvokedCallback callback2 = createBackCallback();
+ dispatcher.registerOnBackInvokedCallback(
+ callback1, OnBackInvokedDispatcher.PRIORITY_OVERLAY);
+ dispatcher.registerOnBackInvokedCallback(
+ callback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
+ dispatcher.unregisterOnBackInvokedCallback(callback2);
+ dispatcher.unregisterOnBackInvokedCallback(callback1);
+ dispatcher.unregisterOnBackInvokedCallback(callback2);
+ }
+
+ private OnBackInvokedCallback createBackCallback() {
+ return new OnBackInvokedCallback() {
+ @Override
+ public void onBackInvoked() {}
+ };
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/OnBackInvokedDispatcherTestActivity.java b/tests/tests/view/src/android/view/cts/OnBackInvokedDispatcherTestActivity.java
new file mode 100644
index 0000000..233147c
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/OnBackInvokedDispatcherTestActivity.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.view.cts;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.os.Bundle;
+
+public class OnBackInvokedDispatcherTestActivity extends Activity {
+ private Dialog mDialog;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.onbackinvokeddispatcher_layout);
+ mDialog = new Dialog(this, 0);
+ mDialog.setContentView(R.layout.onbackinvokeddispatcher_dialog_layout);
+ mDialog.show();
+ }
+
+ public Dialog getDialog() {
+ return mDialog;
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/SurfaceControlTest.java b/tests/tests/view/src/android/view/cts/SurfaceControlTest.java
new file mode 100644
index 0000000..06ca815
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/SurfaceControlTest.java
@@ -0,0 +1,1081 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
+import static android.view.cts.util.ASurfaceControlTestUtils.getQuadrantBuffer;
+import static android.view.cts.util.ASurfaceControlTestUtils.getSolidBuffer;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.HardwareBuffer;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.cts.surfacevalidator.ASurfaceControlTestActivity;
+import android.view.cts.surfacevalidator.ASurfaceControlTestActivity.MultiRectChecker;
+import android.view.cts.surfacevalidator.ASurfaceControlTestActivity.PixelChecker;
+import android.view.cts.surfacevalidator.PixelColor;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class SurfaceControlTest {
+ static {
+ System.loadLibrary("ctsview_jni");
+ }
+
+ private static final String TAG = SurfaceControlTest.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final int DEFAULT_LAYOUT_WIDTH = 100;
+ private static final int DEFAULT_LAYOUT_HEIGHT = 100;
+
+ private static final PixelColor RED = new PixelColor(PixelColor.RED);
+ private static final PixelColor BLUE = new PixelColor(PixelColor.BLUE);
+ private static final PixelColor MAGENTA = new PixelColor(PixelColor.MAGENTA);
+ private static final PixelColor GREEN = new PixelColor(PixelColor.GREEN);
+ private static final PixelColor YELLOW = new PixelColor(PixelColor.YELLOW);
+
+ @Rule
+ public ActivityScenarioRule<ASurfaceControlTestActivity> mActivityRule =
+ createFullscreenActivityScenarioRule(ASurfaceControlTestActivity.class);
+
+ @Rule
+ public TestName mName = new TestName();
+
+ private ASurfaceControlTestActivity mActivity;
+
+ @Before
+ public void setup() {
+ mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
+ }
+
+ SurfaceControl getHostSurfaceControl() {
+ return mActivity.getSurfaceControl();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // SurfaceHolder.Callbacks
+ ///////////////////////////////////////////////////////////////////////////
+
+ private abstract class BasicSurfaceHolderCallback implements SurfaceHolder.Callback {
+ private final Set<SurfaceControl> mSurfaceControls = new HashSet<>();
+ private final Set<HardwareBuffer> mBuffers = new HashSet<>();
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Canvas canvas = holder.lockCanvas();
+ canvas.drawColor(Color.YELLOW);
+ holder.unlockCanvasAndPost(canvas);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ for (SurfaceControl surfaceControl : mSurfaceControls) {
+ transaction.reparent(surfaceControl, null);
+ }
+ transaction.apply();
+ mSurfaceControls.clear();
+
+ for (HardwareBuffer buffer : mBuffers) {
+ buffer.close();
+ }
+ mBuffers.clear();
+ }
+
+ public SurfaceControl createFromWindow(SurfaceHolder surfaceHolder) {
+ assertNotNull("No parent?", getHostSurfaceControl());
+ SurfaceControl surfaceControl = new SurfaceControl.Builder()
+ .setParent(getHostSurfaceControl())
+ .setName("SurfaceControl_createFromWindowLayer")
+ .setHidden(false)
+ .build();
+ mSurfaceControls.add(surfaceControl);
+ return surfaceControl;
+ }
+
+ public SurfaceControl create(SurfaceControl parentSurfaceControl) {
+ assertNotNull("No parent?", parentSurfaceControl);
+ SurfaceControl surfaceControl = new SurfaceControl.Builder()
+ .setParent(parentSurfaceControl)
+ .setName("SurfaceControl_create")
+ .setHidden(false)
+ .build();
+ mSurfaceControls.add(surfaceControl);
+ return surfaceControl;
+ }
+
+ public void setSolidBuffer(SurfaceControl surfaceControl,
+ int width, int height, int color) {
+ HardwareBuffer buffer = getSolidBuffer(width, height, color);
+ assertNotNull("failed to make solid buffer", buffer);
+ new SurfaceControl.Transaction()
+ .setBuffer(surfaceControl, buffer)
+ .apply();
+ mBuffers.add(buffer);
+ }
+
+ public void setQuadrantBuffer(SurfaceControl surfaceControl,
+ int width, int height, int colorTopLeft, int colorTopRight, int colorBottomRight,
+ int colorBottomLeft) {
+ HardwareBuffer buffer = getQuadrantBuffer(width, height, colorTopLeft, colorTopRight,
+ colorBottomRight, colorBottomLeft);
+ assertNotNull("failed to make solid buffer", buffer);
+ new SurfaceControl.Transaction()
+ .setBuffer(surfaceControl, buffer)
+ .apply();
+ mBuffers.add(buffer);
+ }
+
+ public void setSourceToDefaultDest(SurfaceControl surfaceControl, Rect src) {
+ float scaleY = DEFAULT_LAYOUT_HEIGHT / (float) src.height();
+ float scaleX = DEFAULT_LAYOUT_WIDTH / (float) src.width();
+ new SurfaceControl.Transaction()
+ .setCrop(surfaceControl, new Rect(0, 0,
+ DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT))
+ .setPosition(surfaceControl, -src.left * scaleX, -src.top * scaleY)
+ .setScale(surfaceControl, scaleX, scaleY)
+ .apply();
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Tests
+ ///////////////////////////////////////////////////////////////////////////
+
+ private void verifyTest(SurfaceHolder.Callback callback, PixelChecker pixelChecker) {
+ mActivity.verifyTest(callback, pixelChecker, mName);
+ }
+
+ // INTRO: The following tests run a series of commands and verify the
+ // output based on the number of pixels with a certain color on the display.
+ //
+ // The interface being tested is a NDK api but the only way to record the display
+ // through public apis is in through the SDK. So the test logic and test verification
+ // is in Java but the hooks that call into the NDK api are jni code.
+ //
+ // The set up is done during the surfaceCreated callback. In most cases, the
+ // test uses the opportunity to create a child layer through createFromWindow and
+ // performs operations on the child layer.
+ //
+ // When there is no visible buffer for the layer(s) the color defaults to black.
+ // The test cases allow a +/- 10% error rate. This is based on the error
+ // rate allowed in the SurfaceViewSyncTests
+
+ @Test
+ public void testSurfaceControl_createFromWindow() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ createFromWindow(holder);
+ }
+ },
+ new PixelChecker(PixelColor.YELLOW) { //10000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 9000 && pixelCount < 11000;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceControl_create() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl parentSurfaceControl = createFromWindow(holder);
+ SurfaceControl childSurfaceControl = create(parentSurfaceControl);
+ }
+ },
+ new PixelChecker(PixelColor.YELLOW) { //10000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 9000 && pixelCount < 11000;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceControl_acquire() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+ setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ }
+ },
+ new PixelChecker(PixelColor.RED) { //10000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 9000 && pixelCount < 11000;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setBuffer() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+ setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ }
+ },
+ new PixelChecker(PixelColor.RED) { //10000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 9000 && pixelCount < 11000;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setBuffer_parentAndChild() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl parentSurfaceControl = createFromWindow(holder);
+ SurfaceControl childSurfaceControl = create(parentSurfaceControl);
+
+ setSolidBuffer(parentSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+ DEFAULT_LAYOUT_HEIGHT, PixelColor.BLUE);
+ setSolidBuffer(childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+ DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+ }
+ },
+ new PixelChecker(PixelColor.RED) { //10000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 9000 && pixelCount < 11000;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setBuffer_childOnly() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl parentSurfaceControl = createFromWindow(holder);
+ SurfaceControl childSurfaceControl = create(parentSurfaceControl);
+
+ setSolidBuffer(childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+ DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+ }
+ },
+ new PixelChecker(PixelColor.RED) { //10000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 9000 && pixelCount < 11000;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setVisibility_show() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ new SurfaceControl.Transaction()
+ .setVisibility(surfaceControl, true)
+ .apply();
+ }
+ },
+ new PixelChecker(PixelColor.RED) { //10000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 9000 && pixelCount < 11000;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setVisibility_hide() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ new SurfaceControl.Transaction()
+ .setVisibility(surfaceControl, false)
+ .apply();
+ }
+ },
+ new PixelChecker(PixelColor.YELLOW) { //10000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 9000 && pixelCount < 11000;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setBufferOpaque_opaque() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.TRANSLUCENT_RED);
+ new SurfaceControl.Transaction()
+ .setOpaque(surfaceControl, true)
+ .apply();
+ }
+ },
+ new PixelChecker(PixelColor.RED) { //10000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 9000 && pixelCount < 11000;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setBufferOpaque_translucent() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.TRANSLUCENT_RED);
+ new SurfaceControl.Transaction()
+ .setOpaque(surfaceControl, false)
+ .apply();
+ }
+ },
+ // setBufferOpaque is an optimization that can be used by SurfaceFlinger.
+ // It isn't required to affect SurfaceFlinger's behavior.
+ //
+ // Ideally we would check for a specific blending of red with a layer below
+ // it. Unfortunately we don't know what blending the layer will use and
+ // we don't know what variation the GPU/DPU/blitter might have. Although
+ // we don't know what shade of red might be present, we can at least check
+ // that the optimization doesn't cause the framework to drop the buffer entirely.
+ new PixelChecker(PixelColor.YELLOW) {
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount == 0;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setDestinationRect() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ }
+ },
+ new PixelChecker(PixelColor.RED) { //10000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 9000 && pixelCount < 11000;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setDestinationRect_small() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ new SurfaceControl.Transaction()
+ .setScale(surfaceControl, .4f, .4f)
+ .setPosition(surfaceControl, 10, 10)
+ .apply();
+ }
+ },
+ new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+ @Override
+ public PixelColor getExpectedColor(int x, int y) {
+ if (x >= 10 && x < 50 && y >= 10 && y < 50) {
+ return RED;
+ }
+ return YELLOW;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setDestinationRect_smallScaleFirst() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ new SurfaceControl.Transaction()
+ .setScale(surfaceControl, .4f, .4f)
+ .apply();
+ new SurfaceControl.Transaction()
+ .setPosition(surfaceControl, 10, 10)
+ .apply();
+ }
+ },
+ new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+ @Override
+ public PixelColor getExpectedColor(int x, int y) {
+ if (x >= 10 && x < 50 && y >= 10 && y < 50) {
+ return RED;
+ }
+ return YELLOW;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setDestinationRect_smallPositionFirst() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ new SurfaceControl.Transaction()
+ .setPosition(surfaceControl, 10, 10)
+ .apply();
+ new SurfaceControl.Transaction()
+ .setScale(surfaceControl, .4f, .4f)
+ .apply();
+ }
+ },
+ new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+ @Override
+ public PixelColor getExpectedColor(int x, int y) {
+ if (x >= 10 && x < 50 && y >= 10 && y < 50) {
+ return RED;
+ }
+ return YELLOW;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setDestinationRect_parentSmall() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl parentSurfaceControl = createFromWindow(holder);
+ SurfaceControl childSurfaceControl = create(parentSurfaceControl);
+
+ setSolidBuffer(childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+ DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+ new SurfaceControl.Transaction()
+ .setScale(parentSurfaceControl, .4f, .4f)
+ .setPosition(parentSurfaceControl, 10, 10)
+ .apply();
+ }
+ },
+ new PixelChecker(PixelColor.RED) { //1600
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 1440 && pixelCount < 1760;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setDestinationRect_childSmall() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl parentSurfaceControl = createFromWindow(holder);
+ SurfaceControl childSurfaceControl = create(parentSurfaceControl);
+
+ setSolidBuffer(childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+ DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+ new SurfaceControl.Transaction()
+ .setScale(childSurfaceControl, .4f, .4f)
+ .setPosition(childSurfaceControl, 10, 10)
+ .apply();
+ }
+ },
+ new PixelChecker(PixelColor.RED) { //1600
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 1440 && pixelCount < 1760;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setDestinationRect_extraLarge() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ new SurfaceControl.Transaction()
+ .setScale(surfaceControl, 3f, 3f)
+ .setPosition(surfaceControl, -100, -100)
+ .apply();
+ }
+ },
+ new PixelChecker(PixelColor.RED) { //10000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 9000 && pixelCount < 11000;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setDestinationRect_childExtraLarge() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl parentSurfaceControl = createFromWindow(holder);
+ SurfaceControl childSurfaceControl = create(parentSurfaceControl);
+
+ setSolidBuffer(childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+ DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+ new SurfaceControl.Transaction()
+ .setScale(childSurfaceControl, 3f, 3f)
+ .setPosition(childSurfaceControl, -100, -100)
+ .apply();
+ }
+ },
+ new PixelChecker(PixelColor.RED) { //10000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 9000 && pixelCount < 11000;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setDestinationRect_negativeOffset() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ new SurfaceControl.Transaction()
+ .setPosition(surfaceControl, -20, -30)
+ .apply();
+ }
+ },
+ new PixelChecker(PixelColor.RED) { //5600 (w = 80, h = 70)
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 5000 && pixelCount < 6000;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setDestinationRect_outOfParentBounds() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ new SurfaceControl.Transaction()
+ .setPosition(surfaceControl, 50, 50)
+ .apply();
+ }
+ },
+ new PixelChecker(PixelColor.RED) { //2500
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 2250 && pixelCount < 2750;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setDestinationRect_twoLayers() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl1 = createFromWindow(holder);
+ SurfaceControl surfaceControl2 = createFromWindow(holder);
+
+ setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.BLUE);
+ new SurfaceControl.Transaction()
+ .setPosition(surfaceControl1, 10, 10)
+ .setScale(surfaceControl1, .2f, .3f)
+ .apply();
+ new SurfaceControl.Transaction()
+ .setPosition(surfaceControl2, 70, 20)
+ .setScale(surfaceControl2, .2f, .3f)
+ .apply();
+ }
+ },
+
+ new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+ @Override
+ public PixelColor getExpectedColor(int x, int y) {
+ if (x >= 10 && x < 30 && y >= 10 && y < 40) {
+ return RED;
+ } else if (x >= 70 && x < 90 && y >= 20 && y < 50) {
+ return BLUE;
+ } else {
+ return YELLOW;
+ }
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setSourceRect() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+ DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+ PixelColor.MAGENTA, PixelColor.GREEN);
+ }
+ },
+
+ new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+ @Override
+ public PixelColor getExpectedColor(int x, int y) {
+ int halfWidth = DEFAULT_LAYOUT_WIDTH / 2;
+ int halfHeight = DEFAULT_LAYOUT_HEIGHT / 2;
+ if (x < halfWidth && y < halfHeight) {
+ return RED;
+ } else if (x >= halfWidth && y < halfHeight) {
+ return BLUE;
+ } else if (x < halfWidth && y >= halfHeight) {
+ return GREEN;
+ } else {
+ return MAGENTA;
+ }
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setSourceRect_small() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+ DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+ PixelColor.MAGENTA, PixelColor.GREEN);
+ setSourceToDefaultDest(surfaceControl, new Rect(60, 10, 90, 90));
+ }
+ },
+ new PixelChecker(PixelColor.MAGENTA) { //5000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 4500 && pixelCount < 5500;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setSourceRect_smallCentered() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+ DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+ PixelColor.MAGENTA, PixelColor.GREEN);
+ setSourceToDefaultDest(surfaceControl, new Rect(40, 40, 60, 60));
+ }
+ },
+
+ new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+ @Override
+ public PixelColor getExpectedColor(int x, int y) {
+ int halfWidth = DEFAULT_LAYOUT_WIDTH / 2;
+ int halfHeight = DEFAULT_LAYOUT_HEIGHT / 2;
+ if (x < halfWidth && y < halfHeight) {
+ return RED;
+ } else if (x >= halfWidth && y < halfHeight) {
+ return BLUE;
+ } else if (x < halfWidth && y >= halfHeight) {
+ return GREEN;
+ } else {
+ return MAGENTA;
+ }
+ }
+
+ @Override
+ public boolean checkPixels(int matchingPixelCount, int width, int height) {
+ // There will be sampling artifacts along the center line, ignore those
+ return matchingPixelCount > 9000 && matchingPixelCount < 11000;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setCropRect_extraLarge() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+ DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+ PixelColor.MAGENTA, PixelColor.GREEN);
+ new SurfaceControl.Transaction()
+ .setCrop(surfaceControl, new Rect(-50, -50, 150, 150))
+ .apply();
+ }
+ },
+
+ new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+ @Override
+ public PixelColor getExpectedColor(int x, int y) {
+ int halfWidth = DEFAULT_LAYOUT_WIDTH / 2;
+ int halfHeight = DEFAULT_LAYOUT_HEIGHT / 2;
+ if (x < halfWidth && y < halfHeight) {
+ return RED;
+ } else if (x >= halfWidth && y < halfHeight) {
+ return BLUE;
+ } else if (x < halfWidth && y >= halfHeight) {
+ return GREEN;
+ } else {
+ return MAGENTA;
+ }
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setCropRect_badOffset() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+ DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+ PixelColor.MAGENTA, PixelColor.GREEN);
+ new SurfaceControl.Transaction()
+ .setCrop(surfaceControl, new Rect(-50, -50, 50, 50))
+ .apply();
+ }
+ },
+ new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+ @Override
+ public PixelColor getExpectedColor(int x, int y) {
+ int halfWidth = DEFAULT_LAYOUT_WIDTH / 2;
+ int halfHeight = DEFAULT_LAYOUT_HEIGHT / 2;
+ if (x < halfWidth && y < halfHeight) {
+ return RED;
+ } else {
+ return YELLOW;
+ }
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setTransform_flipH() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+ DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+ PixelColor.MAGENTA, PixelColor.GREEN);
+ new SurfaceControl.Transaction()
+ .setBufferTransform(surfaceControl, 1)
+ .apply();
+ }
+ },
+ new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+ @Override
+ public PixelColor getExpectedColor(int x, int y) {
+ int halfWidth = DEFAULT_LAYOUT_WIDTH / 2;
+ int halfHeight = DEFAULT_LAYOUT_HEIGHT / 2;
+ if (x < halfWidth && y < halfHeight) {
+ return BLUE;
+ } else if (x >= halfWidth && y < halfHeight) {
+ return RED;
+ } else if (x < halfWidth && y >= halfHeight) {
+ return MAGENTA;
+ } else {
+ return GREEN;
+ }
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setTransform_rotate180() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+
+ setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+ DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+ PixelColor.MAGENTA, PixelColor.GREEN);
+ setSourceToDefaultDest(surfaceControl, new Rect(0, 50, 50, 100));
+ new SurfaceControl.Transaction()
+ .setBufferTransform(surfaceControl, 3)
+ .apply();
+ }
+ },
+ new PixelChecker(PixelColor.BLUE) { // 10000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 9000 && pixelCount < 11000;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setDamageRegion_all() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl = createFromWindow(holder);
+ setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+
+ HardwareBuffer blueBuffer = getSolidBuffer(DEFAULT_LAYOUT_WIDTH,
+ DEFAULT_LAYOUT_HEIGHT, PixelColor.BLUE);
+ Region damageRegion = new Region();
+ damageRegion.op(0, 0, 25, 25, Region.Op.UNION);
+ damageRegion.op(25, 25, 100, 100, Region.Op.UNION);
+ new SurfaceControl.Transaction()
+ .setBuffer(surfaceControl, blueBuffer)
+ .setDamageRegion(surfaceControl, damageRegion)
+ .apply();
+ }
+ },
+ new PixelChecker(PixelColor.BLUE) { //10000
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount > 9000 && pixelCount < 11000;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setZOrder_zero() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl1 = createFromWindow(holder);
+ SurfaceControl surfaceControl2 = createFromWindow(holder);
+ setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.MAGENTA);
+
+ new SurfaceControl.Transaction()
+ .setLayer(surfaceControl1, 1)
+ .setLayer(surfaceControl2, 0)
+ .apply();
+ }
+ },
+ new PixelChecker(PixelColor.YELLOW) {
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount == 0;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setZOrder_positive() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl1 = createFromWindow(holder);
+ SurfaceControl surfaceControl2 = createFromWindow(holder);
+ setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.MAGENTA);
+
+ new SurfaceControl.Transaction()
+ .setLayer(surfaceControl1, 1)
+ .setLayer(surfaceControl2, 5)
+ .apply();
+ }
+ },
+ new PixelChecker(PixelColor.RED) {
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount == 0;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setZOrder_negative() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl1 = createFromWindow(holder);
+ SurfaceControl surfaceControl2 = createFromWindow(holder);
+ setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.MAGENTA);
+
+ new SurfaceControl.Transaction()
+ .setLayer(surfaceControl1, 1)
+ .setLayer(surfaceControl2, -15)
+ .apply();
+ }
+ },
+ new PixelChecker(PixelColor.YELLOW) {
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount == 0;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setZOrder_max() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl1 = createFromWindow(holder);
+ SurfaceControl surfaceControl2 = createFromWindow(holder);
+ setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.MAGENTA);
+
+ new SurfaceControl.Transaction()
+ .setLayer(surfaceControl1, 1)
+ .setLayer(surfaceControl2, Integer.MAX_VALUE)
+ .apply();
+ }
+ },
+ new PixelChecker(PixelColor.RED) {
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount == 0;
+ }
+ });
+ }
+
+ @Test
+ public void testSurfaceTransaction_setZOrder_min() {
+ verifyTest(
+ new BasicSurfaceHolderCallback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ SurfaceControl surfaceControl1 = createFromWindow(holder);
+ SurfaceControl surfaceControl2 = createFromWindow(holder);
+ setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.RED);
+ setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+ PixelColor.MAGENTA);
+
+ new SurfaceControl.Transaction()
+ .setLayer(surfaceControl1, 1)
+ .setLayer(surfaceControl2, Integer.MIN_VALUE)
+ .apply();
+ }
+ },
+ new PixelChecker(PixelColor.YELLOW) {
+ @Override
+ public boolean checkPixels(int pixelCount, int width, int height) {
+ return pixelCount == 0;
+ }
+ });
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTest.java b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTest.java
index 645d9fa..7cd613c 100644
--- a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTest.java
+++ b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTest.java
@@ -177,6 +177,38 @@
return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY));
};
+ private static AnimationFactory sFixedSizeWithViewSizeAnimationFactory = view -> {
+ ValueAnimator anim = ValueAnimator.ofInt(0, 100);
+ anim.addUpdateListener(valueAnimator -> {
+ ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
+ layoutParams.width++;
+ if (layoutParams.height++ > 1500) {
+ layoutParams.height = 320;
+ layoutParams.width = 240;
+ }
+ view.setLayoutParams(layoutParams);
+
+ if ((Integer) valueAnimator.getAnimatedValue() % 3 == 0) {
+ ((SurfaceView) view).getHolder().setFixedSize(320, 240);
+ } else if ((Integer) valueAnimator.getAnimatedValue() % 7 == 0) {
+ ((SurfaceView) view).getHolder().setFixedSize(1280, 960);
+ }
+ });
+ return makeInfinite(anim);
+ };
+
+ private static AnimationFactory sFixedSizeAnimationFactory = view -> {
+ ValueAnimator anim = ValueAnimator.ofInt(0, 100);
+ anim.addUpdateListener(valueAnimator -> {
+ if ((Integer) valueAnimator.getAnimatedValue() % 2 == 0) {
+ ((SurfaceView) view).getHolder().setFixedSize(320, 240);
+ } else {
+ ((SurfaceView) view).getHolder().setFixedSize(1280, 960);
+ }
+ });
+ return makeInfinite(anim);
+ };
+
private AnimationFactory sTranslateAnimationFactory = view -> {
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 10f, 30f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 10f, 30f);
@@ -263,6 +295,43 @@
}), mName);
}
+
+ /**
+ * Change requested surface size and SurfaceView size and verify buffers always fill to
+ * SurfaceView size. b/190449942
+ */
+ @Test
+ public void testSurfaceViewFixedSizeWithViewSizeChanges() throws Throwable {
+ mActivity.verifyTest(new AnimationTestCase(
+ sVideoViewFactory,
+ new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
+ sFixedSizeWithViewSizeAnimationFactory,
+ new PixelChecker() {
+ @Override
+ public boolean checkPixels(int blackishPixelCount, int width, int height) {
+ return blackishPixelCount == 0;
+ }
+ }), mName);
+ }
+
+ /**
+ * Change requested surface size and verify buffers always fill to SurfaceView size.
+ * b/194458377
+ */
+ @Test
+ public void testSurfaceViewFixedSizeChanges() throws Throwable {
+ mActivity.verifyTest(new AnimationTestCase(
+ sVideoViewFactory,
+ new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
+ sFixedSizeAnimationFactory,
+ new PixelChecker() {
+ @Override
+ public boolean checkPixels(int blackishPixelCount, int width, int height) {
+ return blackishPixelCount == 0;
+ }
+ }), mName);
+ }
+
@Test
public void testVideoSurfaceViewTranslate() throws Throwable {
mActivity.verifyTest(new AnimationTestCase(
diff --git a/tests/tests/view/src/android/view/cts/VerifyInputEventTest.java b/tests/tests/view/src/android/view/cts/VerifyInputEventTest.java
index c74bd43..c4bfb11 100644
--- a/tests/tests/view/src/android/view/cts/VerifyInputEventTest.java
+++ b/tests/tests/view/src/android/view/cts/VerifyInputEventTest.java
@@ -34,6 +34,7 @@
import android.graphics.Point;
import android.hardware.input.InputManager;
import android.os.SystemClock;
+import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -65,7 +66,7 @@
@RunWith(AndroidJUnit4.class)
public class VerifyInputEventTest {
private static final int NANOS_PER_MILLISECOND = 1000000;
- private static final float STRICT_TOLERANCE = 0;
+ private static final float EPSILON = 0.001f;
private static final int INJECTED_EVENT_DEVICE_ID = KeyCharacterMap.VIRTUAL_KEYBOARD;
private InputManager mInputManager;
@@ -158,6 +159,7 @@
final long downTime = SystemClock.uptimeMillis();
MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
point.x, point.y, 0 /*metaState*/);
+ downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mAutomation.injectInputEvent(downEvent, true);
MotionEvent received = waitForMotion();
VerifiedInputEvent verified = mInputManager.verifyInputEvent(received);
@@ -168,6 +170,7 @@
// Send UP event for consistency
MotionEvent upEvent = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
MotionEvent.ACTION_UP, point.x, point.y, 0 /*metaState*/);
+ upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mAutomation.injectInputEvent(upEvent, true);
waitForMotion();
}
@@ -184,6 +187,7 @@
final long downTime = SystemClock.uptimeMillis();
MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
point.x, point.y, 0 /*metaState*/);
+ downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mAutomation.injectInputEvent(downEvent, true);
waitForMotion(); // we will not be using the received event
VerifiedInputEvent verified = mInputManager.verifyInputEvent(downEvent);
@@ -192,6 +196,7 @@
// Send UP event for consistency
MotionEvent upEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_UP,
point.x, point.y, 0 /*metaState*/);
+ upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mAutomation.injectInputEvent(upEvent, true);
waitForMotion();
}
@@ -207,6 +212,7 @@
final long downTime = SystemClock.uptimeMillis();
MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
point.x, point.y, 0 /*metaState*/);
+ downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mAutomation.injectInputEvent(downEvent, true);
MotionEvent received = waitForMotion();
// use the received event, by modify its action
@@ -217,6 +223,7 @@
// Send UP event for consistency
MotionEvent upEvent = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
MotionEvent.ACTION_UP, point.x, point.y, 0 /*metaState*/);
+ upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mAutomation.injectInputEvent(upEvent, true);
waitForMotion();
}
@@ -263,6 +270,7 @@
MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
point.x, point.y, 1 /*pressure*/, 1 /*size*/, 0 /*metaState*/,
0 /*xPrecision*/, 0 /*yPrecision*/, 1 /*deviceId*/, 0 /*edgeFlags*/);
+ downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mAutomation.injectInputEvent(downEvent, true);
MotionEvent received = waitForMotion();
assertEquals(INJECTED_EVENT_DEVICE_ID, received.getDeviceId());
@@ -276,6 +284,7 @@
MotionEvent.ACTION_UP, point.x, point.y, 0 /*pressure*/, 1 /*size*/,
0 /*metaState*/, 0 /*xPrecision*/, 0 /*yPrecision*/,
1 /*deviceId*/, 0 /*edgeFlags*/);
+ upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mAutomation.injectInputEvent(upEvent, true);
waitForMotion();
}
@@ -327,8 +336,8 @@
assertTrue(verified instanceof VerifiedMotionEvent);
VerifiedMotionEvent verifiedMotion = (VerifiedMotionEvent) verified;
- assertEquals(motionEvent.getRawX(), verifiedMotion.getRawX(), STRICT_TOLERANCE);
- assertEquals(motionEvent.getRawY(), verifiedMotion.getRawY(), STRICT_TOLERANCE);
+ assertEquals(motionEvent.getRawX(), verifiedMotion.getRawX(), EPSILON);
+ assertEquals(motionEvent.getRawY(), verifiedMotion.getRawY(), EPSILON);
assertEquals(motionEvent.getActionMasked(), verifiedMotion.getActionMasked());
assertEquals(motionEvent.getDownTime() * NANOS_PER_MILLISECOND,
verifiedMotion.getDownTimeNanos());
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceEnabledTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceEnabledTest.java
index fb1cb13..32693b4 100644
--- a/tests/tests/view/src/android/view/cts/input/InputDeviceEnabledTest.java
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceEnabledTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.view.cts;
+package android.view.cts.input;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTest.java
index ff99963..ca53a15 100644
--- a/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTest.java
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.view.cts;
+package android.view.cts.input;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -23,6 +23,7 @@
import android.app.Instrumentation;
import android.view.InputDevice;
import android.view.KeyEvent;
+import android.view.cts.R;
import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -30,7 +31,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
import com.android.cts.input.InputJsonParser;
import com.android.cts.input.UinputDevice;
@@ -95,7 +96,7 @@
@Before
public void setup() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
- PollingCheck.waitFor(mActivityRule.getActivity()::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivityRule.getActivity());
mParser = new InputJsonParser(mInstrumentation.getTargetContext());
mKeyLayout = nativeLoadKeyLayout(mParser.readRegisterCommand(R.raw.Generic));
mUinputDevice = new UinputDevice(mInstrumentation, DEVICE_ID, GOOGLE_VENDOR_ID,
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTestActivity.java b/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTestActivity.java
index 5c77984..61efe0f 100644
--- a/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTestActivity.java
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTestActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.view.cts;
+package android.view.cts.input;
import static org.junit.Assert.fail;
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceMultiDeviceKeyEventTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceMultiDeviceKeyEventTest.java
index 31d5e6e..eb53bae 100644
--- a/tests/tests/view/src/android/view/cts/input/InputDeviceMultiDeviceKeyEventTest.java
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceMultiDeviceKeyEventTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.view.cts;
+package android.view.cts.input;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -32,7 +32,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
import com.android.cts.input.UinputDevice;
import org.json.JSONArray;
@@ -91,7 +91,7 @@
@Before
public void setup() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
- PollingCheck.waitFor(mActivityRule.getActivity()::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivityRule.getActivity());
for (int i = 0; i < NUM_DEVICES; i++) {
final int jsonDeviceId = i + 1;
mUinputDevices[i] = new UinputDevice(mInstrumentation, jsonDeviceId,
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java
index a088a31..1d34150 100644
--- a/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.view.cts;
+package android.view.cts.input;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -36,13 +36,13 @@
import android.os.SystemClock;
import android.util.Log;
import android.view.InputDevice;
+import android.view.cts.R;
import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.cts.input.InputJsonParser;
import com.android.cts.input.UinputDevice;
import org.junit.After;
@@ -102,12 +102,10 @@
private InputManager mInputManager;
private UinputDevice mUinputDevice;
- private InputJsonParser mParser;
private Instrumentation mInstrumentation;
private SensorManager mSensorManager;
private HandlerThread mSensorThread = null;
private Handler mSensorHandler = null;
- private int mDeviceId;
private final Object mLock = new Object();
private class Callback extends SensorManager.DynamicSensorCallback {
@@ -373,14 +371,10 @@
mInputManager = mInstrumentation.getTargetContext().getSystemService(InputManager.class);
assertNotNull(mInputManager);
- mParser = new InputJsonParser(mInstrumentation.getTargetContext());
- mDeviceId = mParser.readDeviceId(resourceId);
- String registerCommand = mParser.readRegisterCommand(resourceId);
- final int vendorId = mParser.readVendorId(resourceId);
- final int productId = mParser.readProductId(resourceId);
- mUinputDevice = new UinputDevice(mInstrumentation, mDeviceId,
- vendorId, productId, InputDevice.SOURCE_KEYBOARD, registerCommand);
- mSensorManager = getSensorManager(vendorId, productId);
+ mUinputDevice = UinputDevice.create(mInstrumentation, R.raw.gamepad_sensors_register,
+ InputDevice.SOURCE_KEYBOARD);
+ mSensorManager = getSensorManager(mUinputDevice.getVendorId(),
+ mUinputDevice.getProductId());
assertNotNull(mSensorManager);
mSensorThread = new HandlerThread("SensorThread");
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java
index fca9854..61445ee 100644
--- a/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.view.cts;
+package android.view.cts.input;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -29,6 +29,7 @@
import android.os.VibratorManager;
import android.util.Log;
import android.view.InputDevice;
+import android.view.cts.R;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -61,6 +62,7 @@
private InputJsonParser mParser;
private Instrumentation mInstrumentation;
private VibratorManager mVibratorManager;
+ /** The device id inside the resource file (register command) */
private int mDeviceId;
/**
@@ -85,18 +87,16 @@
@Before
public void setup() {
- final int resourceId = R.raw.google_gamepad_register;
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mInputManager = mInstrumentation.getTargetContext().getSystemService(InputManager.class);
assertNotNull(mInputManager);
mParser = new InputJsonParser(mInstrumentation.getTargetContext());
- mDeviceId = mParser.readDeviceId(resourceId);
- String registerCommand = mParser.readRegisterCommand(resourceId);
- mUinputDevice = new UinputDevice(mInstrumentation, mDeviceId,
- mParser.readVendorId(resourceId), mParser.readProductId(resourceId),
- InputDevice.SOURCE_KEYBOARD, registerCommand);
- mVibratorManager = getVibratorManager(mParser.readVendorId(resourceId),
- mParser.readProductId(resourceId));
+
+ mUinputDevice = UinputDevice.create(mInstrumentation, R.raw.google_gamepad_register,
+ InputDevice.SOURCE_KEYBOARD);
+ mDeviceId = mUinputDevice.getRegisterCommandDeviceId();
+ mVibratorManager = getVibratorManager(mUinputDevice.getVendorId(),
+ mUinputDevice.getProductId());
assertTrue(mVibratorManager != null);
}
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorTest.java
index f2d3397..fc81cbd 100644
--- a/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorTest.java
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.view.cts;
+package android.view.cts.input;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -34,6 +34,7 @@
import android.os.Vibrator.OnVibratorStateChangedListener;
import android.util.Log;
import android.view.InputDevice;
+import android.view.cts.R;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -72,6 +73,7 @@
private InputJsonParser mParser;
private Instrumentation mInstrumentation;
private Vibrator mVibrator;
+ /** The device id inside the resource file (register command) */
private int mDeviceId;
@Rule
@@ -102,20 +104,16 @@
@Before
public void setup() {
- final int resourceId = R.raw.google_gamepad_register;
-
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mInputManager = mInstrumentation.getTargetContext().getSystemService(InputManager.class);
assertNotNull(mInputManager);
mParser = new InputJsonParser(mInstrumentation.getTargetContext());
- mDeviceId = mParser.readDeviceId(resourceId);
- String registerCommand = mParser.readRegisterCommand(resourceId);
- mUinputDevice = new UinputDevice(mInstrumentation, mDeviceId,
- mParser.readVendorId(resourceId), mParser.readProductId(resourceId),
- InputDevice.SOURCE_KEYBOARD, registerCommand);
- mVibrator = getVibrator(mParser.readVendorId(resourceId),
- mParser.readProductId(resourceId));
- assertTrue(mVibrator != null);
+
+ mUinputDevice = UinputDevice.create(mInstrumentation, R.raw.google_gamepad_register,
+ InputDevice.SOURCE_KEYBOARD);
+ mDeviceId = mUinputDevice.getRegisterCommandDeviceId();
+ mVibrator = getVibrator(mUinputDevice.getVendorId(), mUinputDevice.getProductId());
+ assertNotNull(mVibrator);
mVibrator.addVibratorStateListener(mListener);
verify(mListener, timeout(CALLBACK_TIMEOUT_MILLIS)
.times(1)).onVibratorStateChanged(false);
diff --git a/tests/tests/view/src/android/view/cts/input/InputEventTest.java b/tests/tests/view/src/android/view/cts/input/InputEventTest.java
deleted file mode 100644
index 6ce80d3..0000000
--- a/tests/tests/view/src/android/view/cts/input/InputEventTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR 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.input;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Map;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class InputEventTest {
-
- @Test
- public void testKeyCodeToString() {
- assertEquals("KEYCODE_UNKNOWN", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_UNKNOWN));
- assertEquals("KEYCODE_HOME", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_HOME));
- assertEquals("KEYCODE_0", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_0));
- assertEquals("KEYCODE_POWER", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_POWER));
- assertEquals("KEYCODE_A", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_A));
- assertEquals("KEYCODE_SPACE", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_SPACE));
- assertEquals("KEYCODE_MENU", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_MENU));
- assertEquals("KEYCODE_BACK", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_BACK));
- assertEquals("KEYCODE_BUTTON_A", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_BUTTON_A));
- assertEquals("KEYCODE_PROFILE_SWITCH",
- KeyEvent.keyCodeToString(KeyEvent.KEYCODE_PROFILE_SWITCH));
- }
-
- @Test
- public void testAxisFromToString() {
- final Map<Integer, String> axes = new ArrayMap<Integer, String>();
- axes.put(MotionEvent.AXIS_X, "AXIS_X");
- axes.put(MotionEvent.AXIS_Y, "AXIS_Y");
- axes.put(MotionEvent.AXIS_PRESSURE, "AXIS_PRESSURE");
- axes.put(MotionEvent.AXIS_SIZE, "AXIS_SIZE");
- axes.put(MotionEvent.AXIS_TOUCH_MAJOR, "AXIS_TOUCH_MAJOR");
- axes.put(MotionEvent.AXIS_TOUCH_MINOR, "AXIS_TOUCH_MINOR");
- axes.put(MotionEvent.AXIS_TOOL_MAJOR, "AXIS_TOOL_MAJOR");
- axes.put(MotionEvent.AXIS_TOOL_MINOR, "AXIS_TOOL_MINOR");
- axes.put(MotionEvent.AXIS_ORIENTATION, "AXIS_ORIENTATION");
- axes.put(MotionEvent.AXIS_VSCROLL, "AXIS_VSCROLL");
- axes.put(MotionEvent.AXIS_HSCROLL, "AXIS_HSCROLL");
- axes.put(MotionEvent.AXIS_Z, "AXIS_Z");
- axes.put(MotionEvent.AXIS_RX, "AXIS_RX");
- axes.put(MotionEvent.AXIS_RY, "AXIS_RY");
- axes.put(MotionEvent.AXIS_RZ, "AXIS_RZ");
- axes.put(MotionEvent.AXIS_HAT_X, "AXIS_HAT_X");
- axes.put(MotionEvent.AXIS_HAT_Y, "AXIS_HAT_Y");
- axes.put(MotionEvent.AXIS_LTRIGGER, "AXIS_LTRIGGER");
- axes.put(MotionEvent.AXIS_RTRIGGER, "AXIS_RTRIGGER");
- axes.put(MotionEvent.AXIS_THROTTLE, "AXIS_THROTTLE");
- axes.put(MotionEvent.AXIS_RUDDER, "AXIS_RUDDER");
- axes.put(MotionEvent.AXIS_WHEEL, "AXIS_WHEEL");
- axes.put(MotionEvent.AXIS_GAS, "AXIS_GAS");
- axes.put(MotionEvent.AXIS_BRAKE, "AXIS_BRAKE");
- axes.put(MotionEvent.AXIS_DISTANCE, "AXIS_DISTANCE");
- axes.put(MotionEvent.AXIS_TILT, "AXIS_TILT");
- axes.put(MotionEvent.AXIS_SCROLL, "AXIS_SCROLL");
- axes.put(MotionEvent.AXIS_RELATIVE_X, "AXIS_RELATIVE_X");
- axes.put(MotionEvent.AXIS_RELATIVE_Y, "AXIS_RELATIVE_Y");
- axes.put(MotionEvent.AXIS_GENERIC_1, "AXIS_GENERIC_1");
- axes.put(MotionEvent.AXIS_GENERIC_2, "AXIS_GENERIC_2");
- axes.put(MotionEvent.AXIS_GENERIC_3, "AXIS_GENERIC_3");
- axes.put(MotionEvent.AXIS_GENERIC_4, "AXIS_GENERIC_4");
- axes.put(MotionEvent.AXIS_GENERIC_5, "AXIS_GENERIC_5");
- axes.put(MotionEvent.AXIS_GENERIC_6, "AXIS_GENERIC_6");
- axes.put(MotionEvent.AXIS_GENERIC_7, "AXIS_GENERIC_7");
- axes.put(MotionEvent.AXIS_GENERIC_8, "AXIS_GENERIC_8");
- axes.put(MotionEvent.AXIS_GENERIC_9, "AXIS_GENERIC_9");
- axes.put(MotionEvent.AXIS_GENERIC_10, "AXIS_GENERIC_10");
- axes.put(MotionEvent.AXIS_GENERIC_11, "AXIS_GENERIC_11");
- axes.put(MotionEvent.AXIS_GENERIC_12, "AXIS_GENERIC_12");
- axes.put(MotionEvent.AXIS_GENERIC_13, "AXIS_GENERIC_13");
- axes.put(MotionEvent.AXIS_GENERIC_14, "AXIS_GENERIC_14");
- axes.put(MotionEvent.AXIS_GENERIC_15, "AXIS_GENERIC_15");
- axes.put(MotionEvent.AXIS_GENERIC_16, "AXIS_GENERIC_16");
- // As Axes values definition is not continuous from AXIS_RELATIVE_Y to AXIS_GENERIC_1,
- // Need to verify MotionEvent.axisToString returns axis name correctly.
- // Also verify that we are not crashing on those calls, and that the return result on each
- // is not empty. We do expect the two-way call chain of to/from to get us back to the
- // original integer value.
- for (Map.Entry<Integer, String> entry : axes.entrySet()) {
- final int axis = entry.getKey();
- String axisToString = MotionEvent.axisToString(entry.getKey());
- assertFalse(TextUtils.isEmpty(axisToString));
- assertEquals(axisToString, entry.getValue());
- assertEquals(axis, MotionEvent.axisFromString(axisToString));
- }
- }
-}
diff --git a/tests/tests/view/src/android/view/cts/input/OWNERS b/tests/tests/view/src/android/view/cts/input/OWNERS
index 50445ea..bbaec4d 100644
--- a/tests/tests/view/src/android/view/cts/input/OWNERS
+++ b/tests/tests/view/src/android/view/cts/input/OWNERS
@@ -1,4 +1,4 @@
# Bug component: 136048
-lzye@google.com
michaelwr@google.com
+prabirmsp@google.com
svv@google.com
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/cts/util/ASurfaceControlTestUtils.java b/tests/tests/view/src/android/view/cts/util/ASurfaceControlTestUtils.java
index a0926b7..6049389 100644
--- a/tests/tests/view/src/android/view/cts/util/ASurfaceControlTestUtils.java
+++ b/tests/tests/view/src/android/view/cts/util/ASurfaceControlTestUtils.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertTrue;
import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
import android.view.Surface;
public class ASurfaceControlTestUtils {
@@ -170,4 +171,10 @@
long surfaceTransaction, boolean waitForFence, TransactionCompleteListener listener);
public static native void nSurfaceTransaction_setOnCommitCallbackWithoutContext(
long surfaceTransaction, TransactionCompleteListener listener);
+ public static native void nSurfaceTransaction_setFrameTimeline(long surfaceTransaction,
+ long vsyncId);
+
+ public static native HardwareBuffer getSolidBuffer(int width, int height, int color);
+ public static native HardwareBuffer getQuadrantBuffer(int width, int height,
+ int colorTopLeft, int colorTopRight, int colorBottomRight, int colorBottomLeft);
}
diff --git a/tests/tests/view/src/android/view/cts/util/FrameCallbackData.java b/tests/tests/view/src/android/view/cts/util/FrameCallbackData.java
new file mode 100644
index 0000000..9e5cd68
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/util/FrameCallbackData.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.util;
+
+public class FrameCallbackData {
+ static {
+ System.loadLibrary("ctsview_jni");
+ }
+
+ public static class FrameTimeline {
+ FrameTimeline(long vsyncId, long expectedPresentTime, long deadline) {
+ mVsyncId = vsyncId;
+ mExpectedPresentTime = expectedPresentTime;
+ mDeadline = deadline;
+ }
+
+ public long getVsyncId() {
+ return mVsyncId;
+ }
+
+ public long getExpectedPresentTime() {
+ return mExpectedPresentTime;
+ }
+
+ public long getDeadline() {
+ return mDeadline;
+ }
+
+ private long mVsyncId;
+ private long mExpectedPresentTime;
+ private long mDeadline;
+ }
+
+ FrameCallbackData(
+ FrameTimeline[] frameTimelines, int preferredFrameTimelineIndex) {
+ mFrameTimelines = frameTimelines;
+ mPreferredFrameTimelineIndex = preferredFrameTimelineIndex;
+ }
+
+ public FrameTimeline[] getFrameTimelines() {
+ return mFrameTimelines;
+ }
+
+ public int getPreferredFrameTimelineIndex() {
+ return mPreferredFrameTimelineIndex;
+ }
+
+ private FrameTimeline[] mFrameTimelines;
+ private int mPreferredFrameTimelineIndex;
+
+ public static native FrameCallbackData nGetFrameTimelines();
+}
diff --git a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/ASurfaceControlTestActivity.java b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/ASurfaceControlTestActivity.java
index ccecf85..6bdeeb3 100644
--- a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/ASurfaceControlTestActivity.java
+++ b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/ASurfaceControlTestActivity.java
@@ -29,19 +29,28 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.Gravity;
import android.view.PointerIcon;
+import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
+import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
+import org.junit.Assert;
+import org.junit.rules.TestName;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -53,7 +62,7 @@
private static final int DEFAULT_LAYOUT_HEIGHT = 100;
private static final int OFFSET_X = 100;
private static final int OFFSET_Y = 100;
- private static final long WAIT_TIMEOUT_S = 5;
+ public static final long WAIT_TIMEOUT_S = 5;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private volatile boolean mOnWatch;
@@ -103,8 +112,26 @@
mInstrumentation = getInstrumentation();
}
+ public SurfaceControl getSurfaceControl() {
+ return mSurfaceView.getSurfaceControl();
+ }
+
public void verifyTest(SurfaceHolder.Callback surfaceHolderCallback,
- PixelChecker pixelChecker) {
+ PixelChecker pixelChecker, TestName name) {
+ final CountDownLatch readyFence = new CountDownLatch(1);
+ SurfaceHolderCallback surfaceHolderCallbackWrapper = new SurfaceHolderCallback(
+ surfaceHolderCallback,
+ readyFence, mParent.getViewTreeObserver());
+ createSurface(surfaceHolderCallbackWrapper);
+ try {
+ assertTrue("timeout", readyFence.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ } catch (InterruptedException e) {
+ Assert.fail("interrupted");
+ }
+ verifyScreenshot(pixelChecker, name);
+ }
+
+ public void createSurface(SurfaceHolderCallback surfaceHolderCallback) {
try {
mReadyToStart.await(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
@@ -120,15 +147,13 @@
return;
}
- final SurfaceHolderCallback surfaceHolderCallbackWrapper =
- new SurfaceHolderCallback(surfaceHolderCallback);
mHandler.post(() -> {
- mSurfaceView.getHolder().addCallback(surfaceHolderCallbackWrapper);
+ mSurfaceView.getHolder().addCallback(surfaceHolderCallback);
mParent.addView(mSurfaceView, mLayoutParams);
});
- mInstrumentation.waitForIdleSync();
- surfaceHolderCallbackWrapper.waitForSurfaceCreated();
+ }
+ public void verifyScreenshot(PixelChecker pixelChecker, TestName name) {
final CountDownLatch countDownLatch = new CountDownLatch(1);
UiAutomation uiAutomation = mInstrumentation.getUiAutomation();
mHandler.post(() -> {
@@ -151,6 +176,9 @@
Rect bounds = pixelChecker.getBoundsToCheck(swBitmap);
boolean success = pixelChecker.checkPixels(numMatchingPixels, swBitmap.getWidth(),
swBitmap.getHeight());
+ if (!success) {
+ saveFailureCapture(swBitmap, name);
+ }
swBitmap.recycle();
assertTrue("Actual matched pixels:" + numMatchingPixels
@@ -161,6 +189,10 @@
return mSurfaceView;
}
+ public FrameLayout getParentFrameLayout() {
+ return mParent;
+ }
+
public abstract static class MultiRectChecker extends RectChecker {
public MultiRectChecker(Rect boundsToCheck) {
super(boundsToCheck);
@@ -267,16 +299,19 @@
public static class SurfaceHolderCallback implements SurfaceHolder.Callback {
private final SurfaceHolder.Callback mTestCallback;
private final CountDownLatch mSurfaceCreatedLatch;
+ private final ViewTreeObserver mViewTreeObserver;
- SurfaceHolderCallback(SurfaceHolder.Callback callback) {
+ public SurfaceHolderCallback(SurfaceHolder.Callback callback, CountDownLatch readyFence,
+ ViewTreeObserver viewTreeObserver) {
mTestCallback = callback;
- mSurfaceCreatedLatch = new CountDownLatch(1);
+ mSurfaceCreatedLatch = readyFence;
+ mViewTreeObserver = viewTreeObserver;
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
mTestCallback.surfaceCreated(holder);
- mSurfaceCreatedLatch.countDown();
+ mViewTreeObserver.registerFrameCommitCallback(mSurfaceCreatedLatch::countDown);
}
@Override
@@ -289,12 +324,31 @@
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
mTestCallback.surfaceDestroyed(holder);
}
+ }
- public void waitForSurfaceCreated() {
- try {
- mSurfaceCreatedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
+ private void saveFailureCapture(Bitmap failFrame, TestName name) {
+ String directoryName = Environment.getExternalStorageDirectory()
+ + "/" + getClass().getSimpleName()
+ + "/" + name.getMethodName();
+ File testDirectory = new File(directoryName);
+ if (testDirectory.exists()) {
+ String[] children = testDirectory.list();
+ for (String file : children) {
+ new File(testDirectory, file).delete();
}
+ } else {
+ testDirectory.mkdirs();
+ }
+
+ String bitmapName = "frame.png";
+ Log.d(TAG, "Saving file : " + bitmapName + " in directory : " + directoryName);
+
+ File file = new File(directoryName, bitmapName);
+ try (FileOutputStream fileStream = new FileOutputStream(file)) {
+ failFrame.compress(Bitmap.CompressFormat.PNG, 85, fileStream);
+ fileStream.flush();
+ } catch (IOException e) {
+ e.printStackTrace();
}
}
}
diff --git a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelColor.java b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelColor.java
index ab71181..e3f4b4e 100644
--- a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelColor.java
+++ b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelColor.java
@@ -24,8 +24,7 @@
public static final int MAGENTA = 0xFFFF00FF;
public static final int WHITE = 0xFFFFFFFF;
- public static final int TRANSPARENT_RED = 0x7F0000FF;
- public static final int TRANSPARENT_BLUE = 0x7FFF0000;
+ public static final int TRANSLUCENT_RED = 0x7F0000FF;
public static final int TRANSPARENT = 0x00000000;
// Default to black
diff --git a/tests/tests/voiceRecognition/OWNERS b/tests/tests/voiceRecognition/OWNERS
index 88d73a9..01a9524 100644
--- a/tests/tests/voiceRecognition/OWNERS
+++ b/tests/tests/voiceRecognition/OWNERS
@@ -1,4 +1,10 @@
# Bug component: 533220
+volnov@google.com
+andreaambu@google.com
+eugeniom@google.com
+schfan@google.com
+
+# Framework team as backup
adamhe@google.com
augale@google.com
joannechung@google.com
diff --git a/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java b/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
index 5188b54..18f1307 100644
--- a/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
+++ b/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
@@ -179,6 +179,8 @@
public static final String HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT =
"android.intent.action.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT";
+ public static final String HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT =
+ "android.intent.action.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT";
public static final String HOTWORD_DETECTION_SERVICE_ONDETECT_RESULT_INTENT =
"android.intent.action.HOTWORD_DETECTION_SERVICE_ONDETECT_RESULT";
public static final String KEY_SERVICE_TYPE = "serviceType";
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java
index 337d017..ade7824 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java
@@ -214,8 +214,13 @@
@Override
public void onHotwordDetectionServiceInitialized(int status) {
super.onHotwordDetectionServiceInitialized(status);
- Log.i(TAG, "onHotwordDetectionServiceInitialized");
- verifyHotwordDetectionServiceInitializedStatus(status);
+ Log.i(TAG, "onHotwordDetectionServiceInitialized status = " + status);
+ if (status != HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS) {
+ return;
+ }
+ broadcastIntentWithResult(
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
}
@Override
@@ -257,7 +262,7 @@
public void onError() {
Log.i(TAG, "onError");
broadcastIntentWithResult(
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
Utils.HOTWORD_DETECTION_SERVICE_GET_ERROR);
}
@@ -278,7 +283,13 @@
@Override
public void onHotwordDetectionServiceInitialized(int status) {
- verifyHotwordDetectionServiceInitializedStatus(status);
+ Log.i(TAG, "onHotwordDetectionServiceInitialized status = " + status);
+ if (status != HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS) {
+ return;
+ }
+ broadcastIntentWithResult(
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
}
@Override
@@ -363,12 +374,4 @@
mTempParcelFileDescriptor = null;
}
}
-
- private void verifyHotwordDetectionServiceInitializedStatus(int status) {
- if (status == HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS) {
- broadcastIntentWithResult(
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
- }
- }
}
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
index d25632a..98d8e67 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
@@ -124,6 +124,41 @@
}
@Test
+ @RequiresDevice
+ public void testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess()
+ throws Throwable {
+ Thread.sleep(CLEAR_CHIP_MS);
+ final BlockingBroadcastReceiver softwareReceiver = new BlockingBroadcastReceiver(mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT);
+ final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT);
+ softwareReceiver.register();
+ receiver.register();
+
+ // Create SoftwareHotwordDetector
+ perform(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST);
+ // Create AlwaysOnHotwordDetector
+ perform(Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST);
+
+ final Intent softwareIntent = softwareReceiver.awaitForBroadcast(TIMEOUT_MS);
+ softwareReceiver.unregisterQuietly();
+
+ assertThat(softwareIntent).isNull();
+
+ final Intent intent = receiver.awaitForBroadcast(TIMEOUT_MS);
+ receiver.unregisterQuietly();
+
+ assertThat(intent).isNotNull();
+ assertThat(intent.getIntExtra(Utils.KEY_TEST_RESULT, -1)).isEqualTo(
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+
+ verifyDetectedResult(
+ performAndGetDetectionResult(Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST),
+ MainHotwordDetectionService.DETECTED_RESULT);
+ verifyMicrophoneChip(true);
+ }
+
+ @Test
public void testVoiceInteractionService_withoutManageHotwordDetectionPermission_triggerFailure()
throws Throwable {
testHotwordDetection(Utils.VIS_WITHOUT_MANAGE_HOTWORD_DETECTION_PERMISSION_TEST,
@@ -193,7 +228,7 @@
Thread.sleep(CLEAR_CHIP_MS);
// Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
verifyDetectedResult(
@@ -208,7 +243,7 @@
throws Throwable {
// Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
// The HotwordDetectionService can't report any result after recognition is stopped. So
@@ -228,7 +263,7 @@
public void testHotwordDetectionService_concurrentCapture() throws Throwable {
// Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
SystemUtil.runWithShellPermissionIdentity(() -> {
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
index bc43563..328931b 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
@@ -30,6 +30,8 @@
import android.util.Log;
import android.voiceinteraction.common.Utils;
+import androidx.test.rule.ActivityTestRule;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -38,8 +40,6 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import androidx.test.rule.ActivityTestRule;
-
// TODO: ideally we should split testAll() into multiple tests, and run at least one of them
// on instant
@AppModeFull(reason = "DirectActionsTest is enough")
@@ -59,7 +59,8 @@
@Before
public void setUp() throws Exception {
mReceiver = new TestResultsReceiver();
- mContext.registerReceiver(mReceiver, new IntentFilter(Utils.BROADCAST_INTENT));
+ mContext.registerReceiver(mReceiver, new IntentFilter(Utils.BROADCAST_INTENT),
+ Context.RECEIVER_EXPORTED);
startTestActivity();
}
diff --git a/tests/tests/voicesettings/src/android/voicesettings/cts/BroadcastTestBase.java b/tests/tests/voicesettings/src/android/voicesettings/cts/BroadcastTestBase.java
index b734b47..328980a0 100644
--- a/tests/tests/voicesettings/src/android/voicesettings/cts/BroadcastTestBase.java
+++ b/tests/tests/voicesettings/src/android/voicesettings/cts/BroadcastTestBase.java
@@ -130,7 +130,8 @@
mLatch = new CountDownLatch(1);
mActivityDoneReceiver = new ActivityDoneReceiver();
mContext.registerReceiver(mActivityDoneReceiver,
- new IntentFilter(BroadcastUtils.BROADCAST_INTENT + testCaseType.toString()));
+ new IntentFilter(BroadcastUtils.BROADCAST_INTENT + testCaseType.toString()),
+ Context.RECEIVER_EXPORTED);
}
protected boolean startTestAndWaitForBroadcast(BroadcastUtils.TestcaseType testCaseType,
diff --git a/tests/tests/webkit/Android.bp b/tests/tests/webkit/Android.bp
index 30cb45f..d8175dd 100644
--- a/tests/tests/webkit/Android.bp
+++ b/tests/tests/webkit/Android.bp
@@ -31,7 +31,10 @@
"ctstestrunner-axt",
"hamcrest-library",
],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.aidl",
+ ],
// Tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java b/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
index 65cc8e6..4e390c9 100644
--- a/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
@@ -172,7 +172,7 @@
final String url = "http://www.example.com";
final String cookie = "name=test";
mCookieManager.setCookie(url, cookie, null);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
String c = mCookieManager.getCookie(url);
@@ -255,7 +255,7 @@
mCookieManager.removeSessionCookies(null);
allCookies = mCookieManager.getCookie(url);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
String c = mCookieManager.getCookie(url);
@@ -266,7 +266,7 @@
}.run();
mCookieManager.removeAllCookies(null);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return !mCookieManager.hasCookies();
@@ -452,7 +452,7 @@
}
private void waitForCookie(final String url) {
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return mCookieManager.getCookie(url) != null;
diff --git a/tests/tests/webkit/src/android/webkit/cts/ITestProcessService.aidl b/tests/tests/webkit/src/android/webkit/cts/ITestProcessService.aidl
new file mode 100644
index 0000000..173c644
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/ITestProcessService.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.cts;
+
+import android.os.Bundle;
+
+interface ITestProcessService {
+ /**
+ * Runs the given test class.
+ *
+ * <p>This is a sync call.
+ *
+ * @param testClassName the name of a test class that extends {@code
+ * TestProcessClient#TestRunnable}.
+ * @return test result as a bundle. If the test passes, the bundle will be empty. If it fails,
+ * it will contain the failure exception as a Serializable.
+ */
+ Bundle run(String testClassName);
+
+ /**
+ * Terminates the TestProcessService process.
+ *
+ * <p>This is a sync call.
+ */
+ void exit();
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java b/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
index 5204f56..ee6f11f 100644
--- a/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
@@ -23,6 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -37,6 +38,11 @@
mProcess = TestProcessClient.createProcessB(context);
}
+ @After
+ public void tearDown() throws Throwable {
+ mProcess.close();
+ }
+
static class TestCreatePacProcessor extends TestProcessClient.TestRunnable {
@Override
public void run(Context ctx) {
diff --git a/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java b/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
index c2fff9e..da5664e 100644
--- a/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
@@ -35,8 +35,6 @@
import java.util.concurrent.BlockingQueue;
public class PostMessageTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
- public static final long TIMEOUT = 20000L;
-
private WebView mWebView;
private WebViewOnUiThread mOnUiThread;
@@ -96,7 +94,7 @@
}
private void waitForTitle(final String title) {
- new PollingCheck(TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return mOnUiThread.getTitle().equals(title);
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java b/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java
index ba6ae47..0a3946e 100644
--- a/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java
+++ b/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java
@@ -20,11 +20,11 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
import android.os.RemoteException;
import com.android.internal.annotations.GuardedBy;
@@ -32,24 +32,30 @@
import com.google.common.util.concurrent.SettableFuture;
import junit.framework.Assert;
-import junit.framework.AssertionFailedError;
+/**
+ * IPC interface to run tests in a freshly spawned service process.
+ *
+ * CTS test modules usually run all tests in the same process, but some WebView tests
+ * need to verify things that only happen once per process. This client interface allows
+ * two separate service processes to be created which are guaranteed to be freshly launched
+ * and to not have loaded the WebView implementation before the test runs. The caller must
+ * close() the client once it's done with it to allow the service process to exit.
+ * The two service processes are identical to each other (A and B are arbitrary labels); we
+ * have two in case a test needs to run more than one thing at once.
+ */
class TestProcessClient extends Assert implements AutoCloseable, ServiceConnection {
private Context mContext;
- static final long REMOTE_TIMEOUT_MS = 5000;
-
private static final long CONNECT_TIMEOUT_MS = 5000;
private Object mLock = new Object();
- @GuardedBy("mLock")
- private Messenger mService;
- @GuardedBy("mLock")
- private Integer mLastResult;
- @GuardedBy("mLock")
- private Throwable mLastException;
- private final Messenger mReplyHandler = new Messenger(new ReplyHandler(Looper.getMainLooper()));
+ @GuardedBy("mLock")
+ private ITestProcessService mService;
+
+ @GuardedBy("mLock")
+ private boolean mIsConnectionClosed = false;
public static TestProcessClient createProcessA(Context context) throws Throwable {
return new TestProcessClient(context, TestProcessServiceA.class);
@@ -59,22 +65,18 @@
return new TestProcessClient(context, TestProcessServiceB.class);
}
- /**
- * Subclass this to implement test code to run on the service side.
- */
- static abstract class TestRunnable extends Assert {
+ /** Subclass this to implement test code to run on the service side. */
+ abstract static class TestRunnable extends Assert {
public abstract void run(Context ctx) throws Throwable;
}
- /**
- * Subclass this to implement test code that runs on the main looper on the service side.
- */
- static abstract class UiThreadTestRunnable extends TestRunnable {
+ /** Subclass this to implement test code that runs on the main looper on the service side. */
+ abstract static class UiThreadTestRunnable extends TestRunnable {
// A handler for the main thread.
private static final Handler sMainThreadHandler = new Handler(Looper.getMainLooper());
@Override
- final public void run(Context ctx) throws Throwable {
+ public final void run(Context ctx) throws Throwable {
final SettableFuture<Void> exceptionPropagatingFuture = SettableFuture.create();
sMainThreadHandler.post(new Runnable() {
@Override
@@ -95,6 +97,7 @@
static class ProcessFreshChecker extends TestRunnable {
private static Object sFreshLock = new Object();
+
@GuardedBy("sFreshLock")
private static boolean sFreshProcess = true;
@@ -104,10 +107,9 @@
if (!sFreshProcess) {
fail("Service process was unexpectedly reused");
}
- sFreshProcess = true;
+ sFreshProcess = false;
}
}
-
}
private TestProcessClient(Context context, Class service) throws Throwable {
@@ -124,48 +126,35 @@
}
// Check that we're using an actual fresh process.
- // 1000ms timeout is plenty since the service is already running.
- run(ProcessFreshChecker.class, 1000);
+ run(ProcessFreshChecker.class);
}
public void run(Class runnableClass) throws Throwable {
- run(runnableClass, REMOTE_TIMEOUT_MS);
- }
-
- public void run(Class runnableClass, long timeoutMs) throws Throwable {
- Message m = Message.obtain(null, TestProcessService.MSG_RUN_TEST);
- m.replyTo = mReplyHandler;
- m.getData().putString(TestProcessService.TEST_CLASS_KEY, runnableClass.getName());
- int result;
- Throwable exception;
+ Bundle result;
synchronized (mLock) {
- mService.send(m);
- if (mLastResult == null) {
- mLock.wait(timeoutMs);
- if (mLastResult == null) {
- fail("Timeout waiting for result");
- }
- }
- result = mLastResult;
- mLastResult = null;
- exception = mLastException;
- mLastException = null;
+ result = mService.run(runnableClass.getName());
}
- if (result == TestProcessService.REPLY_EXCEPTION) {
+ Throwable exception =
+ (Throwable) result.getSerializable(TestProcessService.REPLY_EXCEPTION_KEY);
+ if (exception != null) {
throw exception;
- } else if (result != TestProcessService.REPLY_OK) {
- fail("Unknown result from service: " + result);
}
}
public void close() {
synchronized (mLock) {
- if (mService != null) {
- try {
- mService.send(Message.obtain(null, TestProcessService.MSG_EXIT_PROCESS));
- } catch (RemoteException e) {}
- mService = null;
- mContext.unbindService(this);
+ if (mIsConnectionClosed) {
+ return;
+ }
+ mIsConnectionClosed = true;
+ try {
+ if (mService != null) {
+ mService.exit();
+ fail("This should result in a DeadObjectException");
+ }
+ } catch (DeadObjectException e) {
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
}
}
}
@@ -173,7 +162,7 @@
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
synchronized (mLock) {
- mService = new Messenger(service);
+ mService = ITestProcessService.Stub.asInterface(service);
mLock.notify();
}
}
@@ -183,26 +172,10 @@
synchronized (mLock) {
mService = null;
mContext.unbindService(this);
- mLastResult = TestProcessService.REPLY_EXCEPTION;
- mLastException = new AssertionFailedError("Service disconnected unexpectedly");
mLock.notify();
- }
- }
-
- private class ReplyHandler extends Handler {
- ReplyHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- synchronized (mLock) {
- mLastResult = msg.what;
- if (msg.what == TestProcessService.REPLY_EXCEPTION) {
- mLastException = (Throwable) msg.getData().getSerializable(
- TestProcessService.REPLY_EXCEPTION_KEY);
- }
- mLock.notify();
+ // Service wasn't explicitly disconnected in the close() method.
+ if (!mIsConnectionClosed) {
+ fail("Service disconnected unexpectedly");
}
}
}
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestProcessClientTest.java b/tests/tests/webkit/src/android/webkit/cts/TestProcessClientTest.java
new file mode 100644
index 0000000..f605f64
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/TestProcessClientTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.cts;
+
+import android.content.Context;
+import android.os.Looper;
+import android.test.InstrumentationTestCase;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import junit.framework.Assert;
+import junit.framework.AssertionFailedError;
+
+import java.io.IOException;
+
+/**
+ * Test various scenarios of using {@link TestProcessService} and {@link TestProcessClient}
+ * framework to run tests cases in freshly created test processes.
+ */
+public class TestProcessClientTest extends InstrumentationTestCase {
+
+ static class TestRunningOnUiThread extends TestProcessClient.UiThreadTestRunnable {
+ @Override
+ protected void runOnUiThread(Context ctx) throws Throwable {
+ Assert.assertTrue(
+ "Test is not running on the main thread",
+ Looper.getMainLooper().isCurrentThread());
+ }
+ }
+
+ static class TestRunningOnDefaultThread extends TestProcessClient.TestRunnable {
+ private static Looper sLooper;
+
+ @Override
+ public void run(Context ctx) throws Throwable {
+ Assert.assertFalse(
+ "Default thread should be different from the main thread",
+ Looper.getMainLooper().isCurrentThread());
+ if (sLooper == null) {
+ sLooper = Looper.myLooper();
+ Assert.assertNotNull("The default thread should have a looper", sLooper);
+ } else {
+ Assert.assertTrue(
+ "Test cases should run on the same thread", sLooper.isCurrentThread());
+ }
+ }
+ }
+
+ public void testRunDifferentRunnables() throws Throwable {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ try (TestProcessClient process = TestProcessClient.createProcessA(context)) {
+ process.run(TestRunningOnUiThread.class);
+ process.run(TestRunningOnDefaultThread.class);
+ process.run(TestRunningOnDefaultThread.class);
+ }
+ }
+
+ static class TestNullPointerException extends TestProcessClient.TestRunnable {
+ @Override
+ public void run(Context ctx) throws Throwable {
+ throw new NullPointerException("Test NullPointerException, should be caught");
+ }
+ }
+
+ /**
+ * Test throwing an exception that is handled by the Parcel class: {@link
+ * Parcel#writeException(java.lang.Exception)}.
+ */
+ public void testThrowingNullPointerException() throws Throwable {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ try (TestProcessClient process = TestProcessClient.createProcessA(context)) {
+ process.run(TestNullPointerException.class);
+ fail("A NullPointerException is expected to be thrown");
+ } catch (NullPointerException e) {
+
+ }
+ }
+
+ static class TestIOException extends TestProcessClient.TestRunnable {
+ @Override
+ public void run(Context ctx) throws Throwable {
+ throw new IOException("Test IOException, should be caught");
+ }
+ }
+
+ /**
+ * Test throwing an exception that is not handled by the Parcel class: {@link
+ * Parcel#writeException(java.lang.Exception)}.
+ */
+ public void testThrowingIOException() throws Throwable {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ try (TestProcessClient process = TestProcessClient.createProcessA(context)) {
+ process.run(TestIOException.class);
+ fail("An IOException is expected to be thrown");
+ } catch (IOException e) {
+ }
+ }
+
+ static class TestFailedAssertion extends TestProcessClient.TestRunnable {
+ @Override
+ public void run(Context ctx) throws Throwable {
+ fail("This assertion should be caught");
+ }
+ }
+
+ /**
+ * Test that junit assertions failures are propagated as expected.
+ */
+ public void testFailedAssertion() throws Throwable {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ try (TestProcessClient process = TestProcessClient.createProcessA(context)) {
+ process.run(TestFailedAssertion.class);
+ fail("An AssertionFailedError is expected to be thrown");
+ } catch (AssertionFailedError e) {
+ Assert.assertEquals("This assertion should be caught", e.getMessage());
+ }
+ }
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java b/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java
index 499d18c..f3c80b2 100644
--- a/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java
+++ b/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java
@@ -17,79 +17,53 @@
package android.webkit.cts;
import android.app.Service;
-import android.content.Context;
import android.content.Intent;
+import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import junit.framework.Assert;
-import junit.framework.AssertionFailedError;
+import com.google.common.util.concurrent.SettableFuture;
// Subclasses are the ones that get actually used, so make this abstract
abstract class TestProcessService extends Service {
- static final int MSG_RUN_TEST = 0;
- static final int MSG_EXIT_PROCESS = 1;
- static final String TEST_CLASS_KEY = "class";
-
- static final int REPLY_OK = 0;
- static final int REPLY_EXCEPTION = 1;
static final String REPLY_EXCEPTION_KEY = "exception";
- @Override
- public IBinder onBind(Intent intent) {
- return mMessenger.getBinder();
- }
-
- final Messenger mMessenger;
+ private final Handler mHandler;
public TestProcessService() {
- HandlerThread backgroundThread = new HandlerThread("TestThread");
- backgroundThread.start();
- mMessenger = new Messenger(new IncomingHandler(backgroundThread.getLooper()));
+ HandlerThread handlerThread = new HandlerThread("TestThread");
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper());
}
- private class IncomingHandler extends Handler {
- IncomingHandler(Looper looper) {
- super(looper);
+ private final ITestProcessService.Stub mBinder = new ITestProcessService.Stub() {
+ @Override
+ public Bundle run(String testClassName) {
+ final SettableFuture<Bundle> testResultFuture = SettableFuture.create();
+ mHandler.post(() -> {
+ Bundle testResultBundle = new Bundle();
+ try {
+ Class testClass = Class.forName(testClassName);
+ TestProcessClient.TestRunnable test =
+ (TestProcessClient.TestRunnable) testClass.newInstance();
+ test.run(TestProcessService.this);
+ } catch (Throwable t) {
+ testResultBundle.putSerializable(REPLY_EXCEPTION_KEY, t);
+ }
+ testResultFuture.set(testResultBundle);
+ });
+ return WebkitUtils.waitForFuture(testResultFuture);
}
@Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_EXIT_PROCESS) {
- System.exit(0);
- }
-
- try {
- if (msg.what != MSG_RUN_TEST) {
- throw new AssertionFailedError("Unknown service message " + msg.what);
- }
-
- String testClassName = msg.getData().getString(TEST_CLASS_KEY);
- Class testClass = Class.forName(testClassName);
- TestProcessClient.TestRunnable test =
- (TestProcessClient.TestRunnable) testClass.newInstance();
- test.run(TestProcessService.this);
- } catch (Throwable t) {
- try {
- Message m = Message.obtain(null, REPLY_EXCEPTION);
- m.getData().putSerializable(REPLY_EXCEPTION_KEY, t);
- msg.replyTo.send(m);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- return;
- }
-
- try {
- msg.replyTo.send(Message.obtain(null, REPLY_OK));
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
+ public void exit() {
+ System.exit(0);
}
+ };
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
}
}
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebBackForwardListTest.java b/tests/tests/webkit/src/android/webkit/cts/WebBackForwardListTest.java
index bd81019..b40e672 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebBackForwardListTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebBackForwardListTest.java
@@ -27,8 +27,6 @@
public class WebBackForwardListTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
- private static final int TEST_TIMEOUT = 10000;
-
private WebViewOnUiThread mOnUiThread;
public WebBackForwardListTest() {
@@ -85,7 +83,7 @@
}
private void checkBackForwardList(final String... url) {
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
if (mOnUiThread.getProgress() < 100) {
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
index dbfcfa2..9be1561 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
@@ -44,7 +44,6 @@
@AppModeFull
public class WebChromeClientTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
- private static final long TEST_TIMEOUT = 5000L;
private static final String JAVASCRIPT_UNLOAD = "javascript unload";
private static final String LISTENER_ADDED = "listener added";
private static final String TOUCH_RECEIVED = "touch received";
@@ -95,7 +94,7 @@
String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webChromeClient.hadOnProgressChanged();
@@ -114,7 +113,7 @@
String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webChromeClient.hadOnReceivedTitle();
@@ -146,7 +145,7 @@
String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webChromeClient.hadOnReceivedIcon();
@@ -171,7 +170,7 @@
mOnUiThread.loadUrlAndWaitForCompletion(mWebServer.
getAssetUrl(TestHtmlConstants.JS_WINDOW_URL));
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webChromeClient.hadOnCreateWindow();
@@ -179,7 +178,7 @@
}.run();
if (expectWindowClose) {
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webChromeClient.hadOnCloseWindow();
@@ -273,7 +272,7 @@
String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_ALERT_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webChromeClient.hadOnJsAlert();
@@ -298,7 +297,7 @@
String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_CONFIRM_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webChromeClient.hadOnJsConfirm();
@@ -325,14 +324,14 @@
String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_PROMPT_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webChromeClient.hadOnJsPrompt();
}
}.run();
// the result returned by the client gets set as the page title
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return mOnUiThread.getTitle().equals(promptResult);
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java b/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
index cae5ff8..45529ec 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
@@ -30,7 +30,6 @@
@AppModeFull
public class WebHistoryItemTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
- private final static long TEST_TIMEOUT = 10000;
private CtsTestServer mWebServer;
private WebViewOnUiThread mOnUiThread;
private WebIconDatabase mIconDb;
@@ -95,7 +94,7 @@
String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return waitForIconClient.receivedIcon();
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
index 513db9b..02a0b22 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
@@ -67,8 +67,6 @@
*/
@AppModeFull
public class WebSettingsTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
-
- private static final int WEBVIEW_TIMEOUT = 5000;
private static final String LOG_TAG = "WebSettingsTest";
private final String EMPTY_IMAGE_HEIGHT = "0";
@@ -515,7 +513,7 @@
mSettings.setJavaScriptCanOpenWindowsAutomatically(false);
assertFalse(mSettings.getJavaScriptCanOpenWindowsAutomatically());
mOnUiThread.loadUrl(mWebServer.getAssetUrl(TestHtmlConstants.POPUP_URL));
- new PollingCheck(WEBVIEW_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return "Popup blocked".equals(mOnUiThread.getTitle());
@@ -536,7 +534,7 @@
mSettings.setJavaScriptEnabled(true);
assertTrue(mSettings.getJavaScriptEnabled());
loadAssetUrl(TestHtmlConstants.JAVASCRIPT_URL);
- new PollingCheck(WEBVIEW_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return "javascript on".equals(mOnUiThread.getTitle());
@@ -546,7 +544,7 @@
mSettings.setJavaScriptEnabled(false);
assertFalse(mSettings.getJavaScriptEnabled());
loadAssetUrl(TestHtmlConstants.JAVASCRIPT_URL);
- new PollingCheck(WEBVIEW_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return "javascript off".equals(mOnUiThread.getTitle());
@@ -737,7 +735,7 @@
mSettings.setJavaScriptEnabled(true);
mOnUiThread.loadUrlAndWaitForCompletion(url);
- new PollingCheck(WEBVIEW_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
protected boolean check() {
return "Loaded".equals(mOnUiThread.getTitle());
}
@@ -764,7 +762,7 @@
mSettings.setJavaScriptEnabled(true);
mOnUiThread.loadUrlAndWaitForCompletion(url);
- new PollingCheck(WEBVIEW_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return "Loaded".equals(mOnUiThread.getTitle());
@@ -1297,7 +1295,7 @@
}
private void waitForNonEmptyImage() {
- new PollingCheck(WEBVIEW_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return !EMPTY_IMAGE_HEIGHT.equals(mOnUiThread.getTitle());
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
index 0161540..dd10626 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
@@ -51,7 +51,6 @@
@AppModeFull
public class WebViewClientTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
- private static final long TEST_TIMEOUT = 5000;
private static final String TEST_URL = "http://www.example.com/";
private WebViewOnUiThread mOnUiThread;
@@ -78,7 +77,7 @@
final WebViewCtsActivity activity = getActivity();
WebView webview = activity.getWebView();
if (webview != null) {
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return activity.hasWindowFocus();
@@ -178,13 +177,13 @@
final int childCallCount = childWebViewClient.getShouldOverrideUrlLoadingCallCount();
mOnUiThread.loadUrl(mWebServer.getAssetUrl(TestHtmlConstants.BLANK_TAG_URL));
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return childWebViewClient.hasOnPageFinishedCalled();
}
}.run();
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return childWebViewClient.getShouldOverrideUrlLoadingCallCount() > childCallCount;
@@ -197,7 +196,7 @@
final int childCallCount = childWebViewClient.getShouldOverrideUrlLoadingCallCount();
final int mainCallCount = mainWebViewClient.getShouldOverrideUrlLoadingCallCount();
clickOnLinkUsingJs("link", childWebViewOnUiThread);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return childWebViewClient.getShouldOverrideUrlLoadingCallCount() > childCallCount;
@@ -230,21 +229,21 @@
assertFalse(webViewClient.hasOnPageFinishedCalled());
mOnUiThread.loadUrlAndWaitForCompletion(url);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webViewClient.hasOnPageStartedCalled();
}
}.run();
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webViewClient.hasOnLoadResourceCalled();
}
}.run();
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webViewClient.hasOnPageFinishedCalled();
@@ -273,7 +272,7 @@
assertFalse(webViewClient.hasOnReceivedLoginRequest());
mOnUiThread.loadUrlAndWaitForCompletion(url);
assertTrue(webViewClient.hasOnReceivedLoginRequest());
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webViewClient.hasOnReceivedLoginRequest();
@@ -361,7 +360,7 @@
url.equals(mOnUiThread.getUrl()));
// reloading the current URL should trigger the callback
mOnUiThread.reload();
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webViewClient.hasOnFormResubmissionCalled();
@@ -382,7 +381,7 @@
String url2 = mWebServer.getAssetUrl(TestHtmlConstants.BR_TAG_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url1);
mOnUiThread.loadUrlAndWaitForCompletion(url2);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webViewClient.hasDoUpdateVisitedHistoryCalled();
@@ -428,7 +427,7 @@
assertFalse(webViewClient.hasOnUnhandledKeyEventCalled());
sendKeys(KeyEvent.KEYCODE_1);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webViewClient.hasOnUnhandledKeyEventCalled();
@@ -448,7 +447,7 @@
String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url1);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return mOnUiThread.canZoomIn();
@@ -456,7 +455,7 @@
}.run();
assertTrue(mOnUiThread.zoomIn());
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webViewClient.hasOnScaleChangedCalled();
@@ -618,7 +617,7 @@
final MockWebViewClient webViewClient = new MockWebViewClient();
mOnUiThread.setWebViewClient(webViewClient);
mOnUiThread.loadUrl("chrome://kill");
- new PollingCheck(TEST_TIMEOUT * 5) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webViewClient.hasRenderProcessGoneCalled();
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
index f9496ab..1796464 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
@@ -446,7 +446,7 @@
final WebViewCtsActivity activity = getActivity();
mWebView = activity.getWebView();
if (mWebView != null) {
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return activity.hasWindowFocus();
@@ -561,21 +561,24 @@
mOnUiThread.setWebViewClient(webViewClient);
mOnUiThread.clearSslPreferences();
mOnUiThread.loadUrlAndWaitForCompletion(url);
- assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
+ assertTrue("onReceivedSslError should be called",
+ webViewClient.wasOnReceivedSslErrorCalled());
// Load the page again. We expect another call to
// WebViewClient.onReceivedSslError() since we cleared sslpreferences.
mOnUiThread.clearSslPreferences();
webViewClient.resetCallCounts();
mOnUiThread.loadUrlAndWaitForCompletion(url);
- assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
+ assertTrue("onReceivedSslError should be called again after clearing SSL preferences",
+ webViewClient.wasOnReceivedSslErrorCalled());
assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
// Load the page once again, without clearing the sslpreferences.
// Make sure we do not get the callback.
webViewClient.resetCallCounts();
mOnUiThread.loadUrlAndWaitForCompletion(url);
- assertFalse(webViewClient.wasOnReceivedSslErrorCalled());
+ assertFalse("onReceivedSslError should not be called when SSL preferences are not cleared",
+ webViewClient.wasOnReceivedSslErrorCalled());
assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
}
@@ -670,14 +673,16 @@
mOnUiThread.setWebViewClient(webViewClient);
mOnUiThread.clearSslPreferences();
mOnUiThread.loadUrlAndWaitForCompletion(firstUrl);
- assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
+ assertTrue("onReceivedSslError should be called on loading first page",
+ webViewClient.wasOnReceivedSslErrorCalled());
// Load the second page. We don't expect a call to
// WebViewClient.onReceivedSslError(), but the page should load.
webViewClient.resetCallCounts();
final String sameHostUrl = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
mOnUiThread.loadUrlAndWaitForCompletion(sameHostUrl);
- assertFalse(webViewClient.wasOnReceivedSslErrorCalled());
+ assertFalse("onReceivedSslError should not be called on loading second page",
+ webViewClient.wasOnReceivedSslErrorCalled());
assertEquals("Second page", mOnUiThread.getTitle());
}
@@ -693,7 +698,8 @@
mOnUiThread.setWebViewClient(webViewClient);
mOnUiThread.clearSslPreferences();
mOnUiThread.loadUrlAndWaitForCompletion(firstUrl);
- assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
+ assertTrue("onReceivedSslError should be called when request is sent to localhost",
+ webViewClient.wasOnReceivedSslErrorCalled());
// Load the second page. We expect another call to
// WebViewClient.onReceivedSslError().
@@ -703,7 +709,8 @@
final String differentHostUrl = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2).replace(
"localhost", "127.0.0.1");
mOnUiThread.loadUrlAndWaitForCompletion(differentHostUrl);
- assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
+ assertTrue("onReceivedSslError should be called when request is sent to 127.0.0.1",
+ webViewClient.wasOnReceivedSslErrorCalled());
assertEquals("Second page", mOnUiThread.getTitle());
}
@@ -955,7 +962,7 @@
});
// Wait until clearclientcertpreferences clears the preferences. Generally this is just a
// thread hopping.
- new PollingCheck(WebViewTest.TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return cleared.get();
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index e5a0041..abe44ea 100755
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -122,7 +122,6 @@
@AppModeFull
public class WebViewTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
- public static final long TEST_TIMEOUT = 20000L;
private static final int INITIAL_PROGRESS = 100;
private static final String X_REQUESTED_WITH = "X-Requested-With";
private static final String PRINTER_TEST_FILE = "print.pdf";
@@ -169,7 +168,7 @@
final WebViewCtsActivity activity = getActivity();
mWebView = activity.getWebView();
if (mWebView != null) {
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return activity.hasWindowFocus();
@@ -608,7 +607,7 @@
private synchronized String waitForResult() {
while (!mWasProvideResultCalled) {
try {
- wait(TEST_TIMEOUT);
+ wait(WebkitUtils.TEST_TIMEOUT_MS);
} catch (InterruptedException e) {
continue;
}
@@ -883,7 +882,7 @@
});
if (isPictureFilledWithColor(pictureRef.get(), color))
break;
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return listener.callCount > oldCallCount;
@@ -942,7 +941,7 @@
final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.setPictureListener(listener);
mOnUiThread.loadUrlAndWaitForCompletion(url);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return listener.callCount > 0;
@@ -954,7 +953,7 @@
final int oldCallCount = listener.callCount;
final String newUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL);
mOnUiThread.loadUrlAndWaitForCompletion(newUrl);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return listener.callCount > oldCallCount;
@@ -1264,7 +1263,7 @@
};
mOnUiThread.saveWebArchive(baseName, autoName, callback);
- assertTrue(saving.tryAcquire(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+ assertTrue(saving.tryAcquire(WebkitUtils.TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
public void testSaveWebArchive() throws Throwable {
@@ -1532,7 +1531,7 @@
// Wait for UI thread to settle and receive page dimentions from renderer
// such that we can invoke page down.
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return mOnUiThread.pageDown(false);
@@ -1557,7 +1556,7 @@
// jump to the bottom
assertTrue(mOnUiThread.pageDown(true));
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return bottomScrollY == mOnUiThread.getScrollY();
@@ -1566,7 +1565,7 @@
// jump to the top
assertTrue(mOnUiThread.pageUp(true));
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return topScrollY == mOnUiThread.getScrollY();
@@ -1580,7 +1579,7 @@
}
mOnUiThread.loadDataAndWaitForCompletion(
"<html><body></body></html>", "text/html", null);
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return mOnUiThread.getScale() != 0 && mOnUiThread.getContentHeight() != 0
@@ -1620,7 +1619,7 @@
+ "px;margin:0px auto;\">Get the height of HTML content.</p>";
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ "</body></html>", "text/html", null);
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return mOnUiThread.getContentHeight() > pageHeight;
@@ -1630,7 +1629,7 @@
final int extraSpace = mOnUiThread.getContentHeight() - pageHeight;
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ p + "</body></html>", "text/html", null);
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
// |pageHeight| is accurate, |extraSpace| = getContentheight() - |pageHeight|, so it
@@ -1684,7 +1683,7 @@
"width:" + dimension + "px\">Test fling scroll.</p>";
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ "</body></html>", "text/html", null);
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return mOnUiThread.getContentHeight() >= dimension;
@@ -1697,7 +1696,7 @@
mOnUiThread.flingScroll(10000, 10000);
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return mOnUiThread.getScrollX() > previousScrollX &&
@@ -1727,7 +1726,7 @@
handler.reset();
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
mOnUiThread.requestFocusNodeHref(hrefMsg);
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
boolean done = false;
@@ -1752,7 +1751,7 @@
hrefMsg2.setTarget(handler);
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
mOnUiThread.requestFocusNodeHref(hrefMsg2);
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
boolean done = false;
@@ -1830,7 +1829,7 @@
middleX, middleY, 0));
getInstrumentation().waitForIdleSync();
mOnUiThread.requestImageRef(msg);
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
boolean done = false;
@@ -1933,7 +1932,7 @@
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ "</body></html>", "text/html", null);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f;
@@ -1945,7 +1944,7 @@
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ "2" + "</body></html>", "text/html", null);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f;
@@ -1956,7 +1955,7 @@
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ "3" + "</body></html>", "text/html", null);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return Math.abs(0.5 - mOnUiThread.getScale()) < .01f;
@@ -1967,7 +1966,7 @@
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ "4" + "</body></html>", "text/html", null);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f;
@@ -2045,7 +2044,7 @@
assertEquals(2, saveList.getCurrentIndex());
// wait for the list items to get inflated
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return restoreList.getItemAtIndex(0).getUrl() != null &&
@@ -2081,7 +2080,7 @@
String p = "<p style=\"height:" + dimension + "px;width:" + dimension + "px\"> </p>";
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ "</body></html>", "text/html", null);
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return mOnUiThread.getContentHeight() >= dimension;
@@ -2180,7 +2179,7 @@
mOnUiThread.setNetworkAvailable(false);
// Wait for the DOM to receive notification of the network state change.
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return mOnUiThread.getTitle().equals("OFFLINE");
@@ -2190,7 +2189,7 @@
mOnUiThread.setNetworkAvailable(true);
// Wait for the DOM to receive notification of the network state change.
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return mOnUiThread.getTitle().equals("ONLINE");
@@ -2317,7 +2316,7 @@
final String EXPECTED_TITLE = "test";
mOnUiThread.evaluateJavascript("document.title='" + EXPECTED_TITLE + "';", null);
- new PollingCheck(TEST_TIMEOUT) {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return mOnUiThread.getTitle().equals(EXPECTED_TITLE);
@@ -2732,7 +2731,7 @@
private void pollingCheckWebBackForwardList(final String currUrl, final int currIndex,
final int size) {
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
WebBackForwardList list = mWebView.copyBackForwardList();
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewZoomTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewZoomTest.java
index 2f247f2..dc160b2 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewZoomTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewZoomTest.java
@@ -69,7 +69,7 @@
mOnUiThread = new WebViewOnUiThread(mWebView);
mOnUiThread.requestFocus();
- new PollingCheck() {
+ new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return activity.hasWindowFocus();
@@ -108,7 +108,8 @@
}
private void setUpPage() throws Exception {
- assertFalse(mWebViewClient.onScaleChangedCalled());
+ assertFalse("onScaleChanged has already been called before page has been setup",
+ mWebViewClient.onScaleChangedCalled());
assertNull(mWebServer);
// Pass CtsTestserver.SslMode.TRUST_ANY_CLIENT to make the server serve https URLs yet do
// not ask client for client authentication.
@@ -261,11 +262,14 @@
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
- assertFalse(mWebViewClient.onScaleChangedCalled());
+ assertFalse("There is an onScaleChanged before we call setUpPage()",
+ mWebViewClient.onScaleChangedCalled());
setUpPage();
- assertFalse(mWebViewClient.onScaleChangedCalled());
+ assertFalse("There is an onScaleChanged left over from setUpPage()",
+ mWebViewClient.onScaleChangedCalled());
+
assertTrue(mOnUiThread.zoomIn());
ScaleChangedState state = mWebViewClient.waitForNextScaleChange();
assertEquals(
@@ -278,7 +282,8 @@
// that a scale change does *not* happen.
try {
Thread.sleep(500);
- assertFalse(mWebViewClient.onScaleChangedCalled());
+ assertFalse("There is an onScaleChanged left over from previous scale change",
+ mWebViewClient.onScaleChangedCalled());
assertEquals(currScale, mOnUiThread.getScale());
} catch (InterruptedException e) {
fail("Interrupted");
diff --git a/tests/tests/widget/OWNERS b/tests/tests/widget/OWNERS
index 12f176d..2f13877 100644
--- a/tests/tests/widget/OWNERS
+++ b/tests/tests/widget/OWNERS
@@ -1,5 +1,3 @@
# Bug component: 25700
adamp@google.com
mount@google.com
-shepshapard@google.com
-clarabayarri@google.com
diff --git a/tests/tests/widget/res/layout/edittext_layout.xml b/tests/tests/widget/res/layout/edittext_layout.xml
index 7157d92..5be5244 100644
--- a/tests/tests/widget/res/layout/edittext_layout.xml
+++ b/tests/tests/widget/res/layout/edittext_layout.xml
@@ -55,5 +55,12 @@
android:autoSizeTextType="uniform"
android:textSize="50dp"
android:autoSizeStepGranularity="2dp" />
+
+ <EditText
+ android:id="@+id/edittext_conversion_suggestion"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textEnableTextConversionSuggestions"
+ android:text="@string/edit_text" />
</LinearLayout>
</ScrollView>
diff --git a/tests/tests/widget/res/layout/textview_layout.xml b/tests/tests/widget/res/layout/textview_layout.xml
index 6f1c188..8f0c71f 100644
--- a/tests/tests/widget/res/layout/textview_layout.xml
+++ b/tests/tests/widget/res/layout/textview_layout.xml
@@ -453,6 +453,39 @@
android:lineSpacingMultiplier="0.5"
android:lineHeight="@dimen/textview_lineHeight" />
+ <TextView
+ android:id="@+id/textview_line_break_style_default"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text" />
+
+ <TextView
+ android:id="@+id/textview_line_break_style_none"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text"
+ android:lineBreakStyle="none" />
+
+ <TextView
+ android:id="@+id/textview_line_break_style_loose"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text"
+ android:lineBreakStyle="loose" />
+
+ <TextView
+ android:id="@+id/textview_line_break_style_normal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text"
+ android:lineBreakStyle="normal" />
+
+ <TextView
+ android:id="@+id/textview_line_break_style_strict"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text"
+ android:lineBreakStyle="strict" />
</LinearLayout>
</ScrollView>
diff --git a/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java b/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
index f02f893..084aa7c 100644
--- a/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
@@ -83,6 +83,7 @@
import com.android.compatibility.common.util.CtsTouchUtils;
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
import org.hamcrest.MatcherAssert;
import org.junit.Before;
@@ -137,7 +138,7 @@
// Always use the activity context
mContext = activity;
- PollingCheck.waitFor(activity::hasWindowFocus);
+ WindowUtil.waitForFocus(activity);
XmlPullParser parser = mContext.getResources().getXml(R.layout.listview_layout);
WidgetTestUtils.beginDocument(parser, "FrameLayout");
@@ -427,6 +428,8 @@
assertEquals(v.getLeft(), r.left);
assertEquals(v.getTop(), r.top);
assertEquals(v.getBottom(), r.bottom);
+
+
}
@Test
@@ -475,6 +478,54 @@
}
@Test
+ public void testSelectedChildViewEnabled() throws Throwable {
+ // leave touch-mode
+ mInstrumentation.setInTouchMode(false);
+ setAdapter();
+ WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, () -> {
+ mListView.requestFocus();
+ mListView.setSelectionFromTop(1, 0);
+ });
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
+
+ final int enabledState = (new MyListView(mContext)).getEnabledStateConstant();
+ final Drawable d = mListView.getSelector();
+ assertTrue(d.isStateful());
+ int[] state;
+ boolean enabledFound;
+
+ assertTrue(mListView.isSelectedChildViewEnabled());
+
+ // If selectedChildViewEnabled is false, then the selector shouldn't contain ENABLED state.
+ mListView.setSelectedChildViewEnabled(false);
+ assertFalse(mListView.isSelectedChildViewEnabled());
+ mActivityRule.runOnUiThread(() -> mListView.refreshDrawableState());
+ state = d.getState();
+ enabledFound = false;
+ for (int i = state.length - 1; i >= 0; i--) {
+ if (state[i] == enabledState) {
+ enabledFound = true;
+ break;
+ }
+ }
+ assertFalse(enabledFound);
+
+ // If selectedChildViewEnabled is true, then the selector should contain ENABLED state.
+ mListView.setSelectedChildViewEnabled(true);
+ assertTrue(mListView.isSelectedChildViewEnabled());
+ mActivityRule.runOnUiThread(() -> mListView.refreshDrawableState());
+ state = d.getState();
+ enabledFound = false;
+ for (int i = state.length - 1; i >= 0; i--) {
+ if (state[i] == enabledState) {
+ enabledFound = true;
+ break;
+ }
+ }
+ assertTrue(enabledFound);
+ }
+
+ @Test
public void testSetScrollIndicators() throws Throwable {
final Activity activity = mActivityRule.getActivity();
TextView tv1 = (TextView) activity.findViewById(R.id.headerview1);
@@ -683,7 +734,9 @@
final AbsListView.OnItemLongClickListener mockOnItemLongClickListener =
mock(AbsListView.OnItemLongClickListener.class);
- listView.setOnItemLongClickListener(mockOnItemLongClickListener);
+ mActivityRule.runOnUiThread(
+ () -> listView.setOnItemLongClickListener(mockOnItemLongClickListener)
+ );
verifyZeroInteractions(mockOnItemLongClickListener);
@@ -1220,15 +1273,20 @@
mActivityRule.getActivity().setContentView(listView);
listView.setAdapter(mCountriesAdapter);
});
-
View row = listView.getChildAt(0);
- Rect r = new Rect();
- r.set(0, listView.getHeight() - (row.getHeight() >> 1),
- row.getWidth(), listView.getHeight() + (row.getHeight() >> 1));
+ // Initialize the test scrolled down by half the height of the first child.
+ WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, listView, () -> {
+ listView.scrollListBy(row.getHeight() / 2);
+ });
listView.resetIsOnScrollChangedCalled();
assertFalse(listView.isOnScrollChangedCalled());
- listView.requestChildRectangleOnScreen(row, r, true);
+
+ // Scroll the first child back completely into view (back to the top of the AbsListView).
+ Rect r = new Rect();
+ r.set(0, 0, row.getWidth(), row.getHeight());
+ mActivityRule.runOnUiThread(() -> listView.requestChildRectangleOnScreen(row, r, true));
+
assertTrue(listView.isOnScrollChangedCalled());
}
@@ -1401,5 +1459,9 @@
public void resetIsOnScrollChangedCalled() {
mIsOnScrollChangedCalled = false;
}
+
+ public int getEnabledStateConstant() {
+ return ENABLED_STATE_SET[0];
+ }
}
}
diff --git a/tests/tests/widget/src/android/widget/cts/AbsListView_ScrollTest.java b/tests/tests/widget/src/android/widget/cts/AbsListView_ScrollTest.java
index 15ba71b..d770869 100644
--- a/tests/tests/widget/src/android/widget/cts/AbsListView_ScrollTest.java
+++ b/tests/tests/widget/src/android/widget/cts/AbsListView_ScrollTest.java
@@ -43,8 +43,8 @@
import com.android.compatibility.common.util.CtsTouchUtils;
import com.android.compatibility.common.util.CtsTouchUtils.EventInjectionListener;
-import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.Before;
import org.junit.Rule;
@@ -90,7 +90,7 @@
final Activity activity = mActivityRule.getActivity();
- PollingCheck.waitFor(() -> activity.hasWindowFocus());
+ WindowUtil.waitForFocus(activity);
mCountriesAdapter = new ArrayAdapter<>(mContext,
R.layout.listitemfixed_layout, COUNTRY_LIST);
diff --git a/tests/tests/widget/src/android/widget/cts/AutoCompleteTextViewTest.java b/tests/tests/widget/src/android/widget/cts/AutoCompleteTextViewTest.java
index 417025f..828b573 100644
--- a/tests/tests/widget/src/android/widget/cts/AutoCompleteTextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/AutoCompleteTextViewTest.java
@@ -77,6 +77,7 @@
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
import com.google.common.collect.ImmutableList;
@@ -145,7 +146,7 @@
@Before
public void setup() {
mActivity = mActivityRule.getActivity();
- PollingCheck.waitFor(mActivity::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity);
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mAutoCompleteTextView = (AutoCompleteTextView) mActivity
@@ -823,6 +824,15 @@
assertFalse(mAutoCompleteTextView.isPopupShowing());
WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mAutoCompleteTextView, () -> {
+ mAutoCompleteTextView.showDropDown();
+ });
+ assertTrue(mAutoCompleteTextView.isPopupShowing());
+
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_ESCAPE);
+ // KeyEscape will also close the popup.
+ assertFalse(mAutoCompleteTextView.isPopupShowing());
+
+ WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mAutoCompleteTextView, () -> {
mAutoCompleteTextView.dismissDropDown();
mAutoCompleteTextView.setText(STRING_TEST);
});
diff --git a/tests/tests/widget/src/android/widget/cts/CheckedTextViewTest.java b/tests/tests/widget/src/android/widget/cts/CheckedTextViewTest.java
index 4078e74..c02a47c 100644
--- a/tests/tests/widget/src/android/widget/cts/CheckedTextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/CheckedTextViewTest.java
@@ -135,9 +135,11 @@
assertTrue(view1.isChecked());
assertTrue(view2.isChecked());
- view0.setChecked(true);
- view1.setChecked(false);
- view2.setChecked(false);
+ mActivityRule.runOnUiThread(() -> {
+ view0.setChecked(true);
+ view1.setChecked(false);
+ view2.setChecked(false);
+ });
assertTrue(view0.isChecked());
assertFalse(view1.isChecked());
assertFalse(view2.isChecked());
diff --git a/tests/tests/widget/src/android/widget/cts/DialerFilterTest.java b/tests/tests/widget/src/android/widget/cts/DialerFilterTest.java
index e3fe5bd..c3c70f0 100644
--- a/tests/tests/widget/src/android/widget/cts/DialerFilterTest.java
+++ b/tests/tests/widget/src/android/widget/cts/DialerFilterTest.java
@@ -46,8 +46,8 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.CtsKeyEventUtil;
-import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.Before;
import org.junit.Rule;
@@ -70,7 +70,7 @@
public void setup() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mActivity = mActivityRule.getActivity();
- PollingCheck.waitFor(mActivity::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity);
mDialerFilter = (DialerFilter) mActivity.findViewById(R.id.dialer_filter);
}
diff --git a/tests/tests/widget/src/android/widget/cts/EditTextTest.java b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
index 5ea4bee..b5f7263 100755
--- a/tests/tests/widget/src/android/widget/cts/EditTextTest.java
+++ b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
@@ -29,6 +29,7 @@
import android.graphics.Point;
import android.text.Editable;
import android.text.InputFilter;
+import android.text.InputType;
import android.text.Layout;
import android.text.Spanned;
import android.text.TextUtils;
@@ -693,4 +694,32 @@
assertEquals(et.getFilters()[0], myFilter);
}
+ @UiThreadTest
+ @Test
+ public void testInputTypeForConversionSuggestions() {
+ EditText editText = new EditText(mActivity);
+ editText.setInputType(EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS);
+ editText.setText(mActivity.getResources().getText(R.string.even_more_long_text));
+
+ // The value of the input type is put into the EditorInfo parameter, and then the
+ // InputMethodManager can retrieve the value of the input type from EditorInfo.
+ EditorInfo editorInfo = new EditorInfo();
+ editText.onCreateInputConnection(editorInfo);
+
+ assertEquals(InputType.TYPE_CLASS_TEXT
+ | InputType.TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS,
+ editorInfo.inputType);
+ }
+
+ @UiThreadTest
+ @Test
+ public void testAttributeTextConversionSuggestion() {
+ mActivity.setContentView(R.layout.edittext_layout);
+ TextView tv = (TextView) mActivity.findViewById(
+ R.id.edittext_conversion_suggestion);
+
+ assertEquals(InputType.TYPE_CLASS_TEXT
+ | InputType.TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS, tv.getInputType());
+ }
}
diff --git a/tests/tests/widget/src/android/widget/cts/ExpandableListViewBasicTest.java b/tests/tests/widget/src/android/widget/cts/ExpandableListViewBasicTest.java
index 192d4ba..590f7f6 100644
--- a/tests/tests/widget/src/android/widget/cts/ExpandableListViewBasicTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ExpandableListViewBasicTest.java
@@ -35,8 +35,8 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.CtsKeyEventUtil;
-import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.Before;
import org.junit.Rule;
@@ -62,7 +62,7 @@
public void setup() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mActivity = mActivityRule.getActivity();
- PollingCheck.waitFor(mActivity::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity);
mExpandableListView = mActivity.getExpandableListView();
mAdapter = mExpandableListView.getExpandableListAdapter();
mListUtil = new ListUtil(mExpandableListView, mInstrumentation);
diff --git a/tests/tests/widget/src/android/widget/cts/ExpandableListViewTest.java b/tests/tests/widget/src/android/widget/cts/ExpandableListViewTest.java
index 5baaeeb..ac0f633 100644
--- a/tests/tests/widget/src/android/widget/cts/ExpandableListViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ExpandableListViewTest.java
@@ -55,8 +55,8 @@
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
-import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.Before;
import org.junit.Rule;
@@ -79,7 +79,7 @@
public void setup() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mActivity = mActivityRule.getActivity();
- PollingCheck.waitFor(mActivity::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity);
mExpandableListView = mActivity.getExpandableListView();
}
diff --git a/tests/tests/widget/src/android/widget/cts/ExpandableListViewWithHeadersTest.java b/tests/tests/widget/src/android/widget/cts/ExpandableListViewWithHeadersTest.java
index a6e0796..d18ce75 100644
--- a/tests/tests/widget/src/android/widget/cts/ExpandableListViewWithHeadersTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ExpandableListViewWithHeadersTest.java
@@ -32,8 +32,8 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.CtsKeyEventUtil;
-import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.Before;
import org.junit.Rule;
@@ -56,7 +56,7 @@
public void setup() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mActivity = mActivityRule.getActivity();
- PollingCheck.waitFor(mActivity::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity);
mExpandableListView = mActivity.getExpandableListView();
mListUtil = new ListUtil(mExpandableListView, mInstrumentation);
}
diff --git a/tests/tests/widget/src/android/widget/cts/GridViewTest.java b/tests/tests/widget/src/android/widget/cts/GridViewTest.java
index 22b20a0..ab4a4f8 100644
--- a/tests/tests/widget/src/android/widget/cts/GridViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/GridViewTest.java
@@ -63,8 +63,8 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.CtsKeyEventUtil;
-import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.Before;
import org.junit.Rule;
@@ -95,7 +95,7 @@
mActivity = mActivityRule.getActivity();
mGridView = (GridView) mActivity.findViewById(R.id.gridview);
- PollingCheck.waitFor(mActivity::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity);
}
@Test
diff --git a/tests/tests/widget/src/android/widget/cts/ImageViewTest.java b/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
index b3392b3..c1fcf20 100644
--- a/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
@@ -324,9 +324,11 @@
assertNull(mImageViewRegular.getDrawable());
final Drawable drawable = mActivity.getDrawable(R.drawable.testimage);
+ drawable.setLevel(1);
mImageViewRegular.setImageDrawable(drawable);
assertTrue(mImageViewRegular.isLayoutRequested());
assertNotNull(mImageViewRegular.getDrawable());
+ assertEquals(1, mImageViewRegular.getDrawable().getLevel());
BitmapDrawable testimageBitmap = (BitmapDrawable) drawable;
Drawable imageViewDrawable = mImageViewRegular.getDrawable();
BitmapDrawable imageViewBitmap = (BitmapDrawable) imageViewDrawable;
@@ -335,6 +337,21 @@
@UiThreadTest
@Test
+ public void testSetImageLevelAfterSetImageDrawable() {
+ mImageViewRegular.setImageDrawable(null);
+ assertNull(mImageViewRegular.getDrawable());
+
+ final Drawable drawable = mActivity.getDrawable(R.drawable.testimage);
+ drawable.setLevel(1);
+ mImageViewRegular.setImageDrawable(drawable);
+ assertEquals(1, mImageViewRegular.getDrawable().getLevel());
+ mImageViewRegular.setImageLevel(3);
+ mImageViewRegular.setImageDrawable(drawable);
+ assertEquals(3, mImageViewRegular.getDrawable().getLevel());
+ }
+
+ @UiThreadTest
+ @Test
public void testSetImageBitmap() {
mImageViewRegular.setImageBitmap(null);
// A BitmapDrawable is always created for the ImageView.
diff --git a/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java b/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java
index 8d9e2a2..d26f6c4 100644
--- a/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java
@@ -689,6 +689,21 @@
}
@Test
+ public void testNoDefaultDismissalWithEscapeButton() {
+ mPopupWindowBuilder = new Builder().withDismissListener();
+ WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mActivity.getWindow().getDecorView(),
+ mPopupWindowBuilder::show);
+
+ // Send ESCAPE key event. As we don't have any custom code that dismisses ListPopupWindow,
+ // and ListPopupWindow doesn't track that system-level key event on its own, ListPopupWindow
+ // should stay visible
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_ESCAPE);
+ verify(mPopupWindowBuilder.mOnDismissListener, never()).onDismiss();
+ assertTrue(mPopupWindow.isShowing());
+ }
+
+
+ @Test
public void testCustomDismissalWithBackButton() {
WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mActivity.getWindow().getDecorView(),
() -> {
diff --git a/tests/tests/widget/src/android/widget/cts/MagnifierTest.java b/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
index ef3e4cd..fd39af3 100644
--- a/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
+++ b/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
@@ -52,8 +52,8 @@
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
-import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.Before;
import org.junit.Rule;
@@ -92,7 +92,7 @@
@Before
public void setup() throws Throwable {
mActivity = mActivityRule.getActivity();
- PollingCheck.waitFor(mActivity::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity);
mDisplayMetrics = mActivity.getResources().getDisplayMetrics();
// Do not run the tests, unless the device screen is big enough to fit a magnifier
diff --git a/tests/tests/widget/src/android/widget/cts/SpinnerTest.java b/tests/tests/widget/src/android/widget/cts/SpinnerTest.java
index 02cc7e6..bca2bc7 100755
--- a/tests/tests/widget/src/android/widget/cts/SpinnerTest.java
+++ b/tests/tests/widget/src/android/widget/cts/SpinnerTest.java
@@ -425,6 +425,12 @@
TestUtils.assertAllPixelsOfColor("Drop down should be yellow", dropDownBackground,
dropDownBackground.getBounds().width(), dropDownBackground.getBounds().height(),
false, Color.YELLOW, 1, true);
+
+ waitForHasFocusMS(SPINNER_HAS_FOCUS_DELAY_MS);
+ // Dismiss the popup with the emulated escape key
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_ESCAPE);
+ // Verify that we're not showing the popup
+ PollingCheck.waitFor(() -> !mSpinnerDropdownMode.isPopupShowing());
}
@Test
@@ -463,6 +469,11 @@
PollingCheck.waitFor(() -> mSpinnerDialogMode.isPopupShowing());
// And test that getPopupBackground returns null
assertNull(mSpinnerDialogMode.getPopupBackground());
+
+ // Use emulated escape key to close popup
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_ESCAPE);
+ // Verify that we're not showing the popup
+ PollingCheck.waitFor(() -> !mSpinnerDropdownMode.isPopupShowing());
}
private void waitForHasFocusMS(int milliseconds) {
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewFadingEdgeTest.java b/tests/tests/widget/src/android/widget/cts/TextViewFadingEdgeTest.java
index e3e1fe1..911e402 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewFadingEdgeTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewFadingEdgeTest.java
@@ -37,8 +37,8 @@
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
-import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.Before;
import org.junit.Rule;
@@ -139,7 +139,7 @@
@Before
public void setup() {
mActivity = mActivityRule.getActivity();
- PollingCheck.waitFor(mActivity::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity);
}
@Test
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewReceiveContentTest.java b/tests/tests/widget/src/android/widget/cts/TextViewReceiveContentTest.java
index e44acde..9aceba3 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewReceiveContentTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewReceiveContentTest.java
@@ -69,7 +69,7 @@
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.After;
import org.junit.Assert;
@@ -102,7 +102,7 @@
@Before
public void before() {
mActivity = mActivityRule.getActivity();
- PollingCheck.waitFor(mActivity::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity);
mTextView = mActivity.findViewById(R.id.textview_text);
mDefaultReceiver = new TextViewOnReceiveContentListener();
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index c24510a..aafe309 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -66,6 +66,7 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.fonts.FontStyle;
+import android.graphics.text.LineBreakConfig;
import android.icu.lang.UCharacter;
import android.net.Uri;
import android.os.Bundle;
@@ -171,6 +172,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
/**
* Test {@link TextView}.
@@ -8548,6 +8550,14 @@
mTextView.setTextMetricsParams(param);
assertTrue(param.equals(mTextView.getTextMetricsParams()));
+
+ LineBreakConfig lbConfig = new LineBreakConfig();
+ lbConfig.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT);
+ mTextView.setLineBreakConfig(lbConfig);
+
+ PrecomputedText.Params resultParams = mTextView.getTextMetricsParams();
+ assertEquals(LineBreakConfig.LINE_BREAK_STYLE_STRICT,
+ resultParams.getLineBreakConfig().getLineBreakStyle());
}
@Test
@@ -8591,6 +8601,80 @@
}
@Test
+ public void testLineBreakConfigDefaultValue() {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ final TextView textView = new TextView(context);
+ LineBreakConfig lbConfig = textView.getLineBreakConfig();
+ assertEquals(LineBreakConfig.LINE_BREAK_STYLE_NONE, lbConfig.getLineBreakStyle());
+ }
+
+ @UiThreadTest
+ @Test
+ public void testSetGetLineBreakConfig() {
+ TextView tv = new TextView(mActivity);
+ LineBreakConfig lbConfig = new LineBreakConfig();
+
+ lbConfig.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_LOOSE);
+ tv.setLineBreakConfig(lbConfig);
+ LineBreakConfig resultLbConfig = tv.getLineBreakConfig();
+ assertEquals(LineBreakConfig.LINE_BREAK_STYLE_LOOSE, resultLbConfig.getLineBreakStyle());
+
+ lbConfig.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_NORMAL);
+ tv.setLineBreakConfig(lbConfig);
+ resultLbConfig = tv.getLineBreakConfig();
+ assertEquals(LineBreakConfig.LINE_BREAK_STYLE_NORMAL, resultLbConfig.getLineBreakStyle());
+
+ lbConfig.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT);
+ tv.setLineBreakConfig(lbConfig);
+ resultLbConfig = tv.getLineBreakConfig();
+ assertEquals(LineBreakConfig.LINE_BREAK_STYLE_STRICT, resultLbConfig.getLineBreakStyle());
+ }
+
+ @Test
+ public void testLineBreakConfigByStyle() {
+ TextView defaultTv = findTextView(R.id.textview_line_break_style_default);
+ TextView noneTv = findTextView(R.id.textview_line_break_style_none);
+ TextView looseTv = findTextView(R.id.textview_line_break_style_loose);
+ TextView normalTv = findTextView(R.id.textview_line_break_style_normal);
+ TextView strictTv = findTextView(R.id.textview_line_break_style_strict);
+
+ assertEquals(LineBreakConfig.LINE_BREAK_STYLE_NONE,
+ defaultTv.getLineBreakConfig().getLineBreakStyle());
+ assertEquals(LineBreakConfig.LINE_BREAK_STYLE_NONE,
+ noneTv.getLineBreakConfig().getLineBreakStyle());
+ assertEquals(LineBreakConfig.LINE_BREAK_STYLE_LOOSE,
+ looseTv.getLineBreakConfig().getLineBreakStyle());
+ assertEquals(LineBreakConfig.LINE_BREAK_STYLE_NORMAL,
+ normalTv.getLineBreakConfig().getLineBreakStyle());
+ assertEquals(LineBreakConfig.LINE_BREAK_STYLE_STRICT,
+ strictTv.getLineBreakConfig().getLineBreakStyle());
+ }
+
+ @Test
+ public void testLineBreakConfigEquals() {
+ LineBreakConfig config1 = new LineBreakConfig();
+ config1.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_LOOSE);
+
+ LineBreakConfig config2 = new LineBreakConfig();
+ config2.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_NORMAL);
+
+ LineBreakConfig config3 = new LineBreakConfig();
+ config3.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT);
+
+ assertFalse(config1.equals(config2));
+ assertFalse(config1.equals(config3));
+ assertFalse(config2.equals(config3));
+ assertFalse(Objects.equals(config1, config2));
+ assertFalse(Objects.equals(config1, config3));
+ assertFalse(Objects.equals(config2, config3));
+
+ LineBreakConfig config4 = new LineBreakConfig();
+ config4.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_LOOSE);
+ assertTrue(config1.equals(config4));
+ assertTrue(Objects.equals(config1, config4));
+ }
+
+ @Test
public void testHyphenationFrequencyDefaultValue() {
final Context context = InstrumentationRegistry.getTargetContext();
final TextView textView = new TextView(context);
diff --git a/tests/tests/widget/src/android/widget/cts/TimePickerTest.java b/tests/tests/widget/src/android/widget/cts/TimePickerTest.java
index 9e4e8b3..6f7bbeb 100644
--- a/tests/tests/widget/src/android/widget/cts/TimePickerTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TimePickerTest.java
@@ -46,7 +46,7 @@
import com.android.compatibility.common.util.CtsKeyEventUtil;
import com.android.compatibility.common.util.CtsTouchUtils;
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.Before;
import org.junit.Rule;
@@ -78,7 +78,7 @@
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mActivity = mActivityRule.getActivity();
mTimePicker = (TimePicker) mActivity.findViewById(R.id.timepicker_clock);
- PollingCheck.waitFor(mActivity::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity);
}
@Test
diff --git a/tests/tests/widget/src/android/widget/cts/ToastTest.java b/tests/tests/widget/src/android/widget/cts/ToastTest.java
index 01339eb..6857891 100644
--- a/tests/tests/widget/src/android/widget/cts/ToastTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ToastTest.java
@@ -505,7 +505,7 @@
lock.notifyAll();
}
};
- manager.addAccessibilityServicesStateChangeListener(listener, null);
+ manager.addAccessibilityServicesStateChangeListener(listener);
try {
TestUtils.waitOn(lock,
() -> manager.getRecommendedTimeoutMillis(0,
diff --git a/tests/tests/widget/src/android/widget/cts/inline/InlineContentViewTest.java b/tests/tests/widget/src/android/widget/cts/inline/InlineContentViewTest.java
index 7ac3bc5..ff8875e 100644
--- a/tests/tests/widget/src/android/widget/cts/inline/InlineContentViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/inline/InlineContentViewTest.java
@@ -32,7 +32,7 @@
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
import org.junit.Before;
import org.junit.Rule;
@@ -60,7 +60,7 @@
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mContext = mInstrumentation.getTargetContext();
mActivity = mActivityRule.getActivity();
- PollingCheck.waitFor(mActivity::hasWindowFocus);
+ WindowUtil.waitForFocus(mActivity);
mInlineContentView = new InlineContentView(mContext);
}
diff --git a/tests/tests/widget29/OWNERS b/tests/tests/widget29/OWNERS
index 03429e3..d415939 100644
--- a/tests/tests/widget29/OWNERS
+++ b/tests/tests/widget29/OWNERS
@@ -1,6 +1,5 @@
# Bug component: 25700
adamp@google.com
mount@google.com
-shepshapard@google.com
clarabayarri@google.com
brufino@google.com
diff --git a/tests/tests/widget29/src/android/widget/cts29/ToastTest.java b/tests/tests/widget29/src/android/widget/cts29/ToastTest.java
index 8269843..725415d 100644
--- a/tests/tests/widget29/src/android/widget/cts29/ToastTest.java
+++ b/tests/tests/widget29/src/android/widget/cts29/ToastTest.java
@@ -284,7 +284,7 @@
lock.notifyAll();
}
};
- manager.addAccessibilityServicesStateChangeListener(listener, null);
+ manager.addAccessibilityServicesStateChangeListener(listener);
try {
TestUtils.waitOn(lock,
() -> manager.getRecommendedTimeoutMillis(0,
diff --git a/tests/tests/wifi/AndroidManifest.xml b/tests/tests/wifi/AndroidManifest.xml
index 0e40a37..d2acb5a 100644
--- a/tests/tests/wifi/AndroidManifest.xml
+++ b/tests/tests/wifi/AndroidManifest.xml
@@ -35,6 +35,8 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
+ <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
+ android:usesPermissionFlags="neverForLocation" />
<!-- usesCleartextTraffic is needed by WifiManagerTest#sendTraffic to send HTTP traffic
(as opposed to HTTPS). -->
diff --git a/tests/tests/wifi/AndroidTest.xml b/tests/tests/wifi/AndroidTest.xml
index 421a9f7..5da40ea 100644
--- a/tests/tests/wifi/AndroidTest.xml
+++ b/tests/tests/wifi/AndroidTest.xml
@@ -43,6 +43,5 @@
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
<option name="mainline-module-package-name" value="com.google.android.wifi" />
- <option name="mainline-module-package-name" value="com.google.android.tethering" />
</object>
</configuration>
diff --git a/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java b/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
index e50d3a6..1e17183 100644
--- a/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
@@ -157,6 +157,14 @@
mBlocker.countDown();
}
+ @Override
+ public void onShutDown() {
+ synchronized (mLock) {
+ mSessions.remove(mSession);
+ }
+ mSession = null;
+ }
+
/**
* Waits for any of the callbacks to be called - or an error (timeout, interruption).
* Returns one of the ATTACHED, ATTACH_FAILED, or ERROR values.
@@ -164,6 +172,7 @@
int waitForAnyCallback() {
try {
boolean noTimeout = mBlocker.await(WAIT_FOR_AWARE_CHANGE_SECS, TimeUnit.SECONDS);
+ mBlocker = new CountDownLatch(1);
if (noTimeout) {
return mCallbackCalled;
} else {
@@ -477,6 +486,12 @@
characteristics.getMaxServiceSpecificInfoLength(), 255);
assertEquals("Match Filter Length", characteristics.getMaxMatchFilterLength(), 255);
assertNotEquals("Cipher suites", characteristics.getSupportedCipherSuites(), 0);
+ assertTrue("Max number of NDP", characteristics.getNumberOfSupportedDataPaths() > 0);
+ assertTrue("Max number of NDI", characteristics.getNumberOfSupportedDataInterfaces() > 0);
+ assertTrue("Max number of Publish sessions",
+ characteristics.getNumberOfSupportedPublishSessions() > 0);
+ assertTrue("Max number of Subscribe sessions",
+ characteristics.getNumberOfSupportedSubscribeSessions() > 0);
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
mWifiAwareManager.enableInstantCommunicationMode(true);
assertEquals(mWifiAwareManager.isInstantCommunicationModeEnabled(),
@@ -548,8 +563,10 @@
return;
}
- WifiAwareSession session = attachAndGetSession();
- session.close();
+ AttachCallbackTest callback = attachAndGetCallback();
+ callback.getSession().close();
+ callback.waitForAnyCallback();
+ assertNull(callback.getSession());
if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
assertFalse(mWifiAwareManager.isDeviceAttached());
}
@@ -1005,4 +1022,14 @@
return session;
}
+
+ // local utilities
+
+ private AttachCallbackTest attachAndGetCallback() {
+ AttachCallbackTest attachCb = new AttachCallbackTest();
+ mWifiAwareManager.attach(attachCb, mHandler);
+ int cbCalled = attachCb.waitForAnyCallback();
+ assertEquals("Wi-Fi Aware attach", AttachCallbackTest.ATTACHED, cbCalled);
+ return attachCb;
+ }
}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java b/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
index 188c2a9..203da55 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
@@ -37,16 +37,15 @@
import android.net.wifi.p2p.WifiP2pManager;
import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceInfo;
-import android.provider.Settings;
import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
+import android.provider.Settings;
import android.util.Log;
import com.android.compatibility.common.util.ShellIdentityUtils;
import com.android.compatibility.common.util.SystemUtil;
-import java.util.Arrays;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.BitSet;
import java.util.LinkedList;
import java.util.List;
@@ -725,11 +724,9 @@
});
resetResponse(mMyResponse);
- ShellIdentityUtils.invokeWithShellPermissions(() -> {
- mWifiP2pManager.stopListening(mWifiP2pChannel, mActionListener);
- assertTrue(waitForServiceResponse(mMyResponse));
- assertTrue(mMyResponse.success);
- });
+ mWifiP2pManager.stopListening(mWifiP2pChannel, mActionListener);
+ assertTrue(waitForServiceResponse(mMyResponse));
+ assertTrue(mMyResponse.success);
}
public void testP2pService() {
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java b/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
index 30be41c..2d24d22 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
@@ -23,12 +23,12 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
import static android.net.wifi.WifiUsabilityStatsEntry.ContentionTimeStats;
-import static android.net.wifi.WifiUsabilityStatsEntry.RadioStats;
-import static android.net.wifi.WifiUsabilityStatsEntry.RateStats;
import static android.net.wifi.WifiUsabilityStatsEntry.PROBE_STATUS_FAILURE;
import static android.net.wifi.WifiUsabilityStatsEntry.PROBE_STATUS_NO_PROBE;
import static android.net.wifi.WifiUsabilityStatsEntry.PROBE_STATUS_SUCCESS;
import static android.net.wifi.WifiUsabilityStatsEntry.PROBE_STATUS_UNKNOWN;
+import static android.net.wifi.WifiUsabilityStatsEntry.RadioStats;
+import static android.net.wifi.WifiUsabilityStatsEntry.RateStats;
import static android.net.wifi.WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BE;
import static android.net.wifi.WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BK;
import static android.net.wifi.WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VI;
@@ -41,7 +41,6 @@
import static org.junit.Assert.assertEquals;
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 android.annotation.NonNull;
@@ -49,11 +48,11 @@
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConnectedSessionInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkSpecifier;
import android.net.wifi.WifiNetworkSuggestion;
import android.net.wifi.WifiUsabilityStatsEntry;
-import android.net.wifi.WifiConnectedSessionInfo;
import android.os.Build;
import android.platform.test.annotations.AppModeFull;
import android.support.test.uiautomator.UiDevice;
@@ -761,7 +760,7 @@
}
return mTestHelper.testConnectionFlowWithSuggestionWithShellIdentity(
testNetwork, suggestionBuilder.build(), executorService,
- restrictedNetworkCapabilities);
+ restrictedNetworkCapabilities, false/* restrictedNetwork */);
}
);
}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyMultiInternetWifiNetworkTest.java b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyMultiInternetWifiNetworkTest.java
new file mode 100644
index 0000000..65eef6d
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyMultiInternetWifiNetworkTest.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.cts;
+
+import static android.os.Process.myUid;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.TransportInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.UiDevice;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.modules.utils.build.SdkLevel;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * Tests multiple concurrent connection flow on devices that support multi STA concurrency
+ * (indicated via {@link WifiManager#isStaConcurrencyForMultiInternetSupported()}.
+ *
+ * Tests the entire connection flow using issuing connectivity manager requests with
+ * network specifier containing bands.
+ *
+ * Assumes that all the saved networks is either open/WPA1/WPA2/WPA3 authenticated network.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class MultiStaConcurrencyMultiInternetWifiNetworkTest extends WifiJUnit4TestBase {
+ private static final String TAG = "MultiStaConcurrencyMultiInternetWifiNetworkTest";
+ private static boolean sWasVerboseLoggingEnabled;
+ private static boolean sWasScanThrottleEnabled;
+ private static boolean sWasWifiEnabled;
+ private static boolean sShouldRunTest = false;
+
+ private Context mContext;
+ private WifiManager mWifiManager;
+ private ConnectivityManager mConnectivityManager;
+ private UiDevice mUiDevice;
+ private ScheduledExecutorService mExecutorService;
+ private TestHelper mTestHelper;
+ // Map from band to list of WifiConfiguration, for matching networks.
+ private Map<Integer, List<WifiConfiguration>> mMatchingNetworksMap;
+ // Map from network SSID to set of bands.
+ private Map<String, Set<Integer>> mMatchingNetworksBands;
+
+ private static final int DURATION_MILLIS = 20_000;
+
+ private final ConnectivityManager.NetworkCallback mNetworkCallback =
+ new ConnectivityManager.NetworkCallback(
+ ConnectivityManager.NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) {
+ @Override
+ public void onLinkPropertiesChanged(@NonNull Network network,
+ @NonNull LinkProperties lp) {
+ final boolean isPrimary = isPrimaryWifiNetwork(
+ mConnectivityManager.getNetworkCapabilities(network));
+ Log.d(TAG, "onLinkPropertiesChanged: " + network + " primary " + isPrimary);
+ }
+
+ @Override
+ public void onCapabilitiesChanged(@NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities) {
+ final boolean isPrimary = isPrimaryWifiNetwork(
+ mConnectivityManager.getNetworkCapabilities(network));
+ Log.d(TAG, "onCapabilitiesChanged: " + network + " primary " + isPrimary
+ + " cap " + networkCapabilities);
+ }
+
+ @Override
+ public void onLost(@NonNull Network network) {
+ final boolean isPrimary = isPrimaryWifiNetwork(
+ mConnectivityManager.getNetworkCapabilities(network));
+ }
+ };
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ // skip the test if WiFi is not supported.
+ // Don't use assumeTrue in @BeforeClass
+ if (!WifiFeature.isWifiSupported(context)) return;
+ if (!SdkLevel.isAtLeastT()) return;
+ sShouldRunTest = true;
+ Log.i(TAG, "sShouldRunTest " + sShouldRunTest);
+
+ WifiManager wifiManager = context.getSystemService(WifiManager.class);
+ assertThat(wifiManager).isNotNull();
+
+ // Turn on verbose logging for tests
+ sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.isVerboseLoggingEnabled());
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.setVerboseLoggingEnabled(true));
+ // Disable scan throttling for tests.
+ sWasScanThrottleEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.isScanThrottleEnabled());
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.setScanThrottleEnabled(false));
+
+ // Enable Wifi
+ ShellIdentityUtils.invokeWithShellPermissions(() -> wifiManager.setWifiEnabled(true));
+ // Make sure wifi is enabled
+ PollingCheck.check("Wifi not enabled", DURATION_MILLIS, () -> wifiManager.isWifiEnabled());
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ if (!sShouldRunTest) return;
+
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ WifiManager wifiManager = context.getSystemService(WifiManager.class);
+ assertThat(wifiManager).isNotNull();
+
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.setScanThrottleEnabled(sWasScanThrottleEnabled));
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.setVerboseLoggingEnabled(sWasVerboseLoggingEnabled));
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.setWifiEnabled(sWasWifiEnabled));
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ assumeTrue(sShouldRunTest);
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ mWifiManager = mContext.getSystemService(WifiManager.class);
+ mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mExecutorService = Executors.newSingleThreadScheduledExecutor();
+ mTestHelper = new TestHelper(mContext, mUiDevice);
+
+ // Skip the test if WiFi is not supported.
+ assumeTrue("Wifi not supported", WifiFeature.isWifiSupported(mContext));
+ // Skip if multi STA for internet feature not supported.
+ assumeTrue("isStaConcurrencyForMultiInternetSupported",
+ mWifiManager.isStaConcurrencyForMultiInternetSupported());
+
+ // Turn screen on
+ mTestHelper.turnScreenOn();
+
+ // Clear any existing app state before each test.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+
+ // This test assumes a CTS test environment with at least 2 connectable bssid's, in
+ // different bands. We need 2 AP's for the test:
+ // 1. Dual-band (DBS) AP [the bands being 2.4 + 5]
+ // 2. Single-band AP with a different SSID.
+ // We need 2 saved networks for the 2 AP's and the device in range to proceed.
+ // The test will check if there are 2 BSSIDs in range and in different bands from
+ // the saved network.
+ List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.getPrivilegedConfiguredNetworks());
+ mMatchingNetworksMap =
+ TestHelper.findMatchingSavedNetworksWithBssidByBand(mWifiManager, savedNetworks);
+ assertWithMessage("Need at least 2 saved network bssids in different bands").that(
+ mMatchingNetworksMap.size()).isAtLeast(2);
+
+ mMatchingNetworksBands = new ArrayMap<>();
+ for (Map.Entry<Integer, List<WifiConfiguration>> entry : mMatchingNetworksMap.entrySet()) {
+ final int band = entry.getKey();
+ for (WifiConfiguration network : entry.getValue()) {
+ if (mMatchingNetworksBands.containsKey(network.SSID)) {
+ mMatchingNetworksBands.get(network.SSID).add(band);
+ } else {
+ mMatchingNetworksBands.put(network.SSID, new HashSet<>(Arrays.asList(band)));
+ }
+ }
+ }
+ // Disconnect networks already connected. Make sure the test starts with no network
+ // connections.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> {
+ mWifiManager.disconnect();
+ });
+
+ // Wait for Wifi to be disconnected.
+ PollingCheck.check(
+ "Wifi not disconnected",
+ DURATION_MILLIS,
+ () -> mTestHelper.getNumWifiConnections() == 0);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (!sShouldRunTest) return;
+ // Re-enable networks.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> {
+ for (WifiConfiguration savedNetwork : mWifiManager.getConfiguredNetworks()) {
+ mWifiManager.enableNetwork(savedNetwork.networkId, false);
+ }
+ setMultiInternetMode(WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED);
+ });
+
+ mExecutorService.shutdownNow();
+ // Clear any existing app state after each test.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+ mTestHelper.turnScreenOff();
+ }
+
+ private boolean isPrimaryWifiNetwork(@Nullable NetworkCapabilities networkCapabilities) {
+ if (networkCapabilities == null) {
+ return false;
+ }
+ final TransportInfo transportInfo = networkCapabilities.getTransportInfo();
+ if (!(transportInfo instanceof WifiInfo)) {
+ return false;
+ }
+ return ((WifiInfo) transportInfo).isPrimary();
+ }
+
+ private void setMultiInternetMode(int multiInternetMode) {
+ mWifiManager.setStaConcurrencyForMultiInternetMode(multiInternetMode);
+ try {
+ PollingCheck.check("Wifi not enabled", DURATION_MILLIS,
+ () -> mWifiManager.isWifiEnabled());
+ } catch (Exception e) {
+ fail("Cant get wifi state");
+ }
+ int mode = mWifiManager.getStaConcurrencyForMultiInternetMode();
+ assertEquals(multiInternetMode, mode);
+ }
+
+ /**
+ * Tests the concurrent connection flow.
+ * 1. Connect to a network using internet connectivity API.
+ * 2. Connect to a network using enabling multi internet API.
+ * 3. Verify that both connections are active.
+ */
+ @Test
+ public void testConnectToSecondaryNetworkWhenConnectedToInternetNetworkMultiAp()
+ throws Exception {
+ assertWithMessage("Need at least 2 saved network ssids in different bands").that(
+ mMatchingNetworksBands.size()).isAtLeast(2);
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> {
+ setMultiInternetMode(WifiManager.WIFI_MULTI_INTERNET_MODE_MULTI_AP);
+ });
+ mTestHelper.testMultiInternetConnectionFlowWithShellIdentity(mExecutorService, true);
+ }
+
+ /**
+ * Tests the concurrent connection flow.
+ * 1. Connect to a network using internet connectivity API.
+ * 2. Connect to a network using enabling multi internet API.
+ * 3. Verify that both connections are active.
+ */
+ @Test
+ public void testConnectToSecondaryNetworkWhenConnectedToInternetNetworkDBS() throws Exception {
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> {
+ setMultiInternetMode(WifiManager.WIFI_MULTI_INTERNET_MODE_DBS_AP);
+ });
+
+ mTestHelper.testMultiInternetConnectionFlowWithShellIdentity(mExecutorService, true);
+ }
+
+ /**
+ * Tests the concurrent connection flow fails without enabling the MultiInternetState.
+ * 1. Connect to a network using internet connectivity API.
+ * 2. Connect to a network using enabling multi internet API.
+ * 3. Verify that both connections are active.
+ */
+ @Test
+ public void testConnectToSecondaryNetworkWhenConnectedToInternetNetworkFail() throws Exception {
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> {
+ setMultiInternetMode(WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED);
+ });
+
+ mTestHelper.testMultiInternetConnectionFlowWithShellIdentity(mExecutorService, false);
+ }
+}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java
index 271035f..8b030da 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java
@@ -244,7 +244,7 @@
.build();
mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
- Set.of(NET_CAPABILITY_OEM_PAID));
+ Set.of(NET_CAPABILITY_OEM_PAID), false/* restrictedNetwork */);
// Ensure that there are 2 wifi connections available for apps.
assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
@@ -266,7 +266,7 @@
.build();
mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
- Set.of(NET_CAPABILITY_OEM_PAID));
+ Set.of(NET_CAPABILITY_OEM_PAID), false);
// Now trigger internet connectivity.
mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
@@ -296,7 +296,7 @@
.build();
mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
- Set.of(NET_CAPABILITY_OEM_PRIVATE));
+ Set.of(NET_CAPABILITY_OEM_PRIVATE), false/* restrictedNetwork */);
// Ensure that there are 2 wifi connections available for apps.
assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
@@ -318,7 +318,7 @@
.build();
mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
- Set.of(NET_CAPABILITY_OEM_PRIVATE));
+ Set.of(NET_CAPABILITY_OEM_PRIVATE), false/* restrictedNetwork */);
// Now trigger internet connectivity.
mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java b/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java
index 98ba803..06ed9a2 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java
@@ -19,9 +19,6 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-import java.nio.ByteBuffer;
-import java.util.List;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -31,13 +28,19 @@
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
+import android.net.wifi.WifiSsid;
import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.ShellIdentityUtils;
import com.android.compatibility.common.util.SystemUtil;
+import java.nio.ByteBuffer;
+import java.util.List;
+
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public class ScanResultTest extends WifiJUnit3TestBase {
private static class MySync {
@@ -46,6 +49,7 @@
private WifiManager mWifiManager;
private WifiLock mWifiLock;
+ private TestHelper mTestHelper;
private static MySync mMySync;
private boolean mWasVerboseLoggingEnabled;
private boolean mWasScanThrottleEnabled;
@@ -67,6 +71,9 @@
private static final long SCAN_FIND_BSSID_WAIT_MSEC = 5_000L;
private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
+ // Note: defined in ScanRequestProxy.java
+ public static final int SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS = 4;
+
private static final String TEST_SSID = "TEST_SSID";
public static final String TEST_BSSID = "04:ac:fe:45:34:10";
public static final String TEST_CAPS = "CCMP";
@@ -119,6 +126,9 @@
mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
assertThat(mWifiManager).isNotNull();
+ mTestHelper = new TestHelper(InstrumentationRegistry.getInstrumentation().getContext(),
+ UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()));
+
// turn on verbose logging for tests
mWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.isVerboseLoggingEnabled());
@@ -283,6 +293,7 @@
ScanResult scanResult = new ScanResult();
scanResult.SSID = TEST_SSID;
+ scanResult.setWifiSsid(WifiSsid.fromUtf8Text(TEST_SSID));
scanResult.BSSID = TEST_BSSID;
scanResult.capabilities = TEST_CAPS;
scanResult.level = TEST_LEVEL;
@@ -291,6 +302,7 @@
ScanResult scanResult2 = new ScanResult(scanResult);
assertThat(scanResult2.SSID).isEqualTo(TEST_SSID);
+ assertThat(scanResult2.getWifiSsid()).isEqualTo(scanResult.getWifiSsid());
assertThat(scanResult2.BSSID).isEqualTo(TEST_BSSID);
assertThat(scanResult2.capabilities).isEqualTo(TEST_CAPS);
assertThat(scanResult2.level).isEqualTo(TEST_LEVEL);
@@ -338,5 +350,33 @@
.that("\"" + scanResultSsidUnquoted + "\"")
.isEqualTo(wifiInfoSsidQuoted);
assertThat(currentNetwork.frequency).isEqualTo(wifiInfo.getFrequency());
+ assertThat(currentNetwork.getSecurityTypes())
+ .asList().contains(wifiInfo.getCurrentSecurityType());
+ }
+
+ /**
+ * Verify that scan throttling is enforced.
+ */
+ public void testScanThrottling() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+
+ // re-enable scan throttling
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.setScanThrottleEnabled(true));
+ mTestHelper.turnScreenOn();
+
+ synchronized (mMySync) {
+ for (int i = 0; i < SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS; ++i) {
+ mWifiManager.startScan();
+ assertTrue("Iteration #" + i,
+ waitForBroadcast(SCAN_WAIT_MSEC, STATE_SCAN_RESULTS_AVAILABLE));
+ }
+
+ mWifiManager.startScan();
+ assertTrue("Should be throttled", waitForBroadcast(SCAN_WAIT_MSEC, STATE_SCAN_FAILURE));
+ }
}
}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
index 98ab11d..02193c3 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
@@ -20,6 +20,7 @@
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.net.ConnectivityManager.NetworkCallback.FLAG_INCLUDE_LOCATION_INFO;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
@@ -27,6 +28,11 @@
import static com.google.common.truth.Truth.assertThat;
+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.annotation.NonNull;
@@ -47,16 +53,19 @@
import android.os.WorkSource;
import android.support.test.uiautomator.UiDevice;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.PollingCheck;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
@@ -104,8 +113,12 @@
private final CountDownLatch mCountDownLatch;
public boolean onAvailableCalled = false;
- TestScanResultsCallback(CountDownLatch countDownLatch) {
- mCountDownLatch = countDownLatch;
+ TestScanResultsCallback() {
+ mCountDownLatch = new CountDownLatch(1);
+ }
+
+ public void await() throws InterruptedException {
+ mCountDownLatch.await(DURATION_MILLIS, TimeUnit.MILLISECONDS);
}
@Override
@@ -125,28 +138,51 @@
*
* @param wifiManager WifiManager service
* @param savedNetworks List of saved networks on the device.
+ * @return List of WifiConfiguration with matching bssid.
*/
public static List<WifiConfiguration> findMatchingSavedNetworksWithBssid(
@NonNull WifiManager wifiManager, @NonNull List<WifiConfiguration> savedNetworks) {
if (savedNetworks.isEmpty()) return Collections.emptyList();
List<WifiConfiguration> matchingNetworksWithBssids = new ArrayList<>();
- CountDownLatch countDownLatch = new CountDownLatch(1);
+ Map<Integer, List<WifiConfiguration>> networksMap =
+ findMatchingSavedNetworksWithBssidByBand(wifiManager, savedNetworks);
+ for (List<WifiConfiguration> configs : networksMap.values()) {
+ matchingNetworksWithBssids.addAll(configs);
+ }
+ return matchingNetworksWithBssids;
+ }
+
+ /**
+ * Loops through all the saved networks available in the scan results. Returns a map of lists of
+ * WifiConfiguration with the matching bssid filled in {@link WifiConfiguration#BSSID}.
+ *
+ * Note:
+ * a) If there are more than 2 networks with the same SSID, but different credential type, then
+ * this matching may pick the wrong one.
+ *
+ * @param wifiManager WifiManager service
+ * @param savedNetworks List of saved networks on the device.
+ * @return Map from band to the list of WifiConfiguration with matching bssid.
+ */
+ public static Map<Integer, List<WifiConfiguration>> findMatchingSavedNetworksWithBssidByBand(
+ @NonNull WifiManager wifiManager, @NonNull List<WifiConfiguration> savedNetworks) {
+ if (savedNetworks.isEmpty()) return Collections.emptyMap();
+ Map<Integer, List<WifiConfiguration>> matchingNetworksWithBssids = new ArrayMap<>();
for (int i = 0; i < SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID; i++) {
// Trigger a scan to get fresh scan results.
- TestScanResultsCallback scanResultsCallback =
- new TestScanResultsCallback(countDownLatch);
+ TestScanResultsCallback scanResultsCallback = new TestScanResultsCallback();
try {
wifiManager.registerScanResultsCallback(
Executors.newSingleThreadExecutor(), scanResultsCallback);
wifiManager.startScan(new WorkSource(myUid()));
// now wait for callback
- countDownLatch.await(DURATION_MILLIS, TimeUnit.MILLISECONDS);
+ scanResultsCallback.await();
} catch (InterruptedException e) {
} finally {
wifiManager.unregisterScanResultsCallback(scanResultsCallback);
}
List<ScanResult> scanResults = wifiManager.getScanResults();
- if (scanResults == null || scanResults.isEmpty()) fail("No scan results available");
+ if (scanResults == null || scanResults.isEmpty()) continue;
for (ScanResult scanResult : scanResults) {
WifiConfiguration matchingNetwork = savedNetworks.stream()
.filter(network -> TextUtils.equals(
@@ -157,7 +193,13 @@
// make a copy in case we have 2 bssid's for the same network.
WifiConfiguration matchingNetworkCopy = new WifiConfiguration(matchingNetwork);
matchingNetworkCopy.BSSID = scanResult.BSSID;
- matchingNetworksWithBssids.add(matchingNetworkCopy);
+ List<WifiConfiguration> bandConfigs = matchingNetworksWithBssids.get(
+ scanResult.getBand());
+ if (bandConfigs == null) {
+ bandConfigs = new ArrayList<>();
+ matchingNetworksWithBssids.put(scanResult.getBand(), bandConfigs);
+ }
+ bandConfigs.add(matchingNetworkCopy);
}
}
if (!matchingNetworksWithBssids.isEmpty()) break;
@@ -226,47 +268,70 @@
.setBssid(MacAddress.fromString(network.BSSID));
}
- private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
- private final CountDownLatch mCountDownLatch;
+ public static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
+ private CountDownLatch mBlocker;
public boolean onAvailableCalled = false;
public boolean onUnavailableCalled = false;
+ public boolean onLostCalled = false;
public NetworkCapabilities networkCapabilities;
- TestNetworkCallback(@NonNull CountDownLatch countDownLatch) {
- mCountDownLatch = countDownLatch;
+ TestNetworkCallback() {
+ mBlocker = new CountDownLatch(1);
}
- TestNetworkCallback(@NonNull CountDownLatch countDownLatch, int flags) {
+ TestNetworkCallback(int flags) {
super(flags);
- mCountDownLatch = countDownLatch;
+ mBlocker = new CountDownLatch(1);
+ }
+
+ public boolean await(long timeout) throws Exception {
+ return mBlocker.await(timeout, TimeUnit.MILLISECONDS);
}
@Override
public void onAvailable(Network network) {
+ Log.i(TAG, "onAvailable " + network);
onAvailableCalled = true;
}
@Override
public void onCapabilitiesChanged(Network network,
NetworkCapabilities networkCapabilities) {
+ Log.i(TAG, "onCapabilitiesChanged " + network);
this.networkCapabilities = networkCapabilities;
- mCountDownLatch.countDown();
+ mBlocker.countDown();
}
@Override
public void onUnavailable() {
+ Log.i(TAG, "onUnavailable ");
onUnavailableCalled = true;
- mCountDownLatch.countDown();
+ mBlocker.countDown();
+ }
+
+ @Override
+ public void onLost(Network network) {
+ onLostCalled = true;
+ mBlocker.countDown();
+ }
+
+ boolean waitForAnyCallback(int timeout) {
+ try {
+ boolean noTimeout = mBlocker.await(timeout, TimeUnit.MILLISECONDS);
+ mBlocker = new CountDownLatch(1);
+ return noTimeout;
+ } catch (InterruptedException e) {
+ return false;
+ }
}
}
- private static TestNetworkCallback createTestNetworkCallback(
- @NonNull CountDownLatch countDownLatch) {
+ private static TestNetworkCallback createTestNetworkCallback() {
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
// flags for NetworkCallback only introduced in S.
- return new TestNetworkCallback(countDownLatch, FLAG_INCLUDE_LOCATION_INFO);
+ return new TestNetworkCallback(FLAG_INCLUDE_LOCATION_INFO);
} else {
- return new TestNetworkCallback(countDownLatch);
+ return new TestNetworkCallback();
}
}
@@ -320,9 +385,8 @@
public ConnectivityManager.NetworkCallback testConnectionFlowWithConnect(
@NonNull WifiConfiguration network) throws Exception {
CountDownLatch countDownLatchAl = new CountDownLatch(1);
- CountDownLatch countDownLatchNr = new CountDownLatch(1);
TestActionListener actionListener = new TestActionListener(countDownLatchAl);
- TestNetworkCallback testNetworkCallback = createTestNetworkCallback(countDownLatchNr);
+ TestNetworkCallback testNetworkCallback = createTestNetworkCallback();
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
@@ -346,8 +410,8 @@
assertThat(actionListener.onSuccessCalled).isTrue();
// Wait for connection to complete & ensure we are connected to the saved network.
- assertThat(countDownLatchNr.await(
- DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+ assertThat(testNetworkCallback.waitForAnyCallback(DURATION_NETWORK_CONNECTION_MILLIS))
+ .isTrue();
assertThat(testNetworkCallback.onAvailableCalled).isTrue();
final WifiInfo wifiInfo = getWifiInfo(testNetworkCallback.networkCapabilities);
assertConnectionEquals(network, wifiInfo);
@@ -379,15 +443,18 @@
* @param restrictedNetworkCapabilities Whether this connection should be restricted with
* the provided capability.
*
+ * @param isRestricted whether the suggestion is for a restricted network
* @return NetworkCallback used for the connection (can be used by client to release the
* connection.
*/
public ConnectivityManager.NetworkCallback testConnectionFlowWithSuggestionWithShellIdentity(
WifiConfiguration network, WifiNetworkSuggestion suggestion,
@NonNull ScheduledExecutorService executorService,
- @NonNull Set<Integer> restrictedNetworkCapabilities) throws Exception {
+ @NonNull Set<Integer> restrictedNetworkCapabilities,
+ boolean isRestricted) throws Exception {
return testConnectionFlowWithSuggestionInternal(
- network, suggestion, executorService, restrictedNetworkCapabilities, true);
+ network, suggestion, executorService, restrictedNetworkCapabilities, true,
+ isRestricted);
}
/**
@@ -402,19 +469,22 @@
* @param restrictedNetworkCapabilities Whether this connection should be restricted with
* the provided capability.
*
+ * @param isRestricted whether the suggestion is for a restricted network
* @return NetworkCallback used for the connection (can be used by client to release the
* connection.
*/
public ConnectivityManager.NetworkCallback testConnectionFlowWithSuggestion(
WifiConfiguration network, WifiNetworkSuggestion suggestion,
@NonNull ScheduledExecutorService executorService,
- @NonNull Set<Integer> restrictedNetworkCapabilities) throws Exception {
+ @NonNull Set<Integer> restrictedNetworkCapabilities,
+ boolean isRestricted) throws Exception {
final UiAutomation uiAutomation =
InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity(NETWORK_SETTINGS, CONNECTIVITY_INTERNAL);
return testConnectionFlowWithSuggestionWithShellIdentity(
- network, suggestion, executorService, restrictedNetworkCapabilities);
+ network, suggestion, executorService, restrictedNetworkCapabilities,
+ isRestricted);
} finally {
uiAutomation.dropShellPermissionIdentity();
}
@@ -441,7 +511,8 @@
try {
uiAutomation.adoptShellPermissionIdentity(NETWORK_SETTINGS, CONNECTIVITY_INTERNAL);
return testConnectionFlowWithSuggestionInternal(
- network, suggestion, executorService, restrictedNetworkCapabilities, false);
+ network, suggestion, executorService, restrictedNetworkCapabilities, false,
+ false/* restrictedNetwork */);
} finally {
uiAutomation.dropShellPermissionIdentity();
}
@@ -457,6 +528,7 @@
* the provided capability.
* @param expectConnectionSuccess Whether to expect connection success or not.
*
+ * @param isRestricted whether the suggestion is for a restricted network
* @return NetworkCallback used for the connection (can be used by client to release the
* connection.
*/
@@ -464,16 +536,15 @@
WifiConfiguration network, WifiNetworkSuggestion suggestion,
@NonNull ScheduledExecutorService executorService,
@NonNull Set<Integer> restrictedNetworkCapabilities,
- boolean expectConnectionSuccess) throws Exception {
- CountDownLatch countDownLatch = new CountDownLatch(1);
+ boolean expectConnectionSuccess, boolean isRestricted) throws Exception {
// File the network request & wait for the callback.
- TestNetworkCallback testNetworkCallback = createTestNetworkCallback(countDownLatch);
+ TestNetworkCallback testNetworkCallback = createTestNetworkCallback();
try {
// File a request for restricted (oem paid) wifi network.
NetworkRequest.Builder nrBuilder = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI)
.addCapability(NET_CAPABILITY_INTERNET);
- if (restrictedNetworkCapabilities.isEmpty()) {
+ if (restrictedNetworkCapabilities.isEmpty() && !isRestricted) {
// If not a restricted connection, a network callback is sufficient.
mConnectivityManager.registerNetworkCallback(
nrBuilder.build(), testNetworkCallback);
@@ -481,6 +552,7 @@
for (Integer restrictedNetworkCapability : restrictedNetworkCapabilities) {
nrBuilder.addCapability(restrictedNetworkCapability);
}
+ nrBuilder.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
mConnectivityManager.requestNetwork(nrBuilder.build(), testNetworkCallback);
}
// Add wifi network suggestion.
@@ -496,22 +568,21 @@
}, 0, DURATION_MILLIS, TimeUnit.MILLISECONDS);
if (expectConnectionSuccess) {
// now wait for connection to complete and wait for callback
- assertThat(countDownLatch.await(
- DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+ assertThat(testNetworkCallback
+ .waitForAnyCallback(DURATION_NETWORK_CONNECTION_MILLIS)).isTrue();
assertThat(testNetworkCallback.onAvailableCalled).isTrue();
final WifiInfo wifiInfo = getWifiInfo(testNetworkCallback.networkCapabilities);
assertConnectionEquals(network, wifiInfo);
- if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
- assertThat(wifiInfo.isTrusted()).isTrue();
- WifiInfo redact = wifiInfo
- .makeCopy(NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION);
- assertThat(wifiInfo.getInformationElements()).isNotNull();
- assertThat(redact.getInformationElements()).isNull();
- assertThat(redact.getApplicableRedactions()).isEqualTo(
- NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION
- | NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS
- | NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS);
- }
+ assertThat(wifiInfo.isTrusted()).isTrue();
+ assertThat(wifiInfo.isRestricted()).isEqualTo(isRestricted);
+ WifiInfo redact = wifiInfo
+ .makeCopy(NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION);
+ assertThat(wifiInfo.getInformationElements()).isNotNull();
+ assertThat(redact.getInformationElements()).isNull();
+ assertThat(redact.getApplicableRedactions()).isEqualTo(
+ NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION
+ | NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS
+ | NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS);
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
// If STA concurrency for restricted connection is supported, this should not
// be the primary connection.
@@ -524,8 +595,8 @@
}
} else {
// now wait for connection to timeout.
- assertThat(countDownLatch.await(
- DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isFalse();
+ assertThat(testNetworkCallback
+ .waitForAnyCallback(DURATION_NETWORK_CONNECTION_MILLIS)).isFalse();
}
} catch (Throwable e /* catch assertions & exceptions */) {
try {
@@ -672,9 +743,8 @@
public ConnectivityManager.NetworkCallback testConnectionFlowWithSpecifierWithShellIdentity(
WifiConfiguration network, WifiNetworkSpecifier specifier, boolean shouldUserReject)
throws Exception {
- CountDownLatch countDownLatch = new CountDownLatch(1);
// File the network request & wait for the callback.
- TestNetworkCallback testNetworkCallback = createTestNetworkCallback(countDownLatch);
+ TestNetworkCallback testNetworkCallback = createTestNetworkCallback();
// Fork a thread to handle the UI interactions.
Thread uiThread = new Thread(() -> {
@@ -703,8 +773,8 @@
// Start the UI interactions.
uiThread.run();
// now wait for callback
- assertThat(countDownLatch.await(
- DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+ assertThat(testNetworkCallback.waitForAnyCallback(DURATION_NETWORK_CONNECTION_MILLIS))
+ .isTrue();
if (shouldUserReject) {
assertThat(testNetworkCallback.onUnavailableCalled).isTrue();
} else {
@@ -771,8 +841,9 @@
public long getNumWifiConnections() {
Network[] networks = mConnectivityManager.getAllNetworks();
return Arrays.stream(networks)
- .filter(n ->
- mConnectivityManager.getNetworkCapabilities(n).hasTransport(TRANSPORT_WIFI))
+ .filter(n -> mConnectivityManager.getNetworkCapabilities(n) != null
+ && mConnectivityManager.getNetworkCapabilities(n)
+ .hasTransport(TRANSPORT_WIFI))
.count();
}
@@ -783,8 +854,7 @@
* @throws Exception
*/
public void assertWifiInternetConnectionAvailable() throws Exception {
- CountDownLatch countDownLatchNr = new CountDownLatch(1);
- TestNetworkCallback testNetworkCallback = createTestNetworkCallback(countDownLatchNr);
+ TestNetworkCallback testNetworkCallback = createTestNetworkCallback();
try {
// File a callback for wifi network.
NetworkRequest.Builder builder = new NetworkRequest.Builder()
@@ -799,8 +869,8 @@
mConnectivityManager.registerNetworkCallback(builder.build(), testNetworkCallback);
// Wait for connection to complete & ensure we are connected to some network capable
// of providing internet access.
- assertThat(countDownLatchNr.await(
- DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+ assertThat(testNetworkCallback.waitForAnyCallback(DURATION_NETWORK_CONNECTION_MILLIS))
+ .isTrue();
assertThat(testNetworkCallback.onAvailableCalled).isTrue();
} finally {
mConnectivityManager.unregisterNetworkCallback(testNetworkCallback);
@@ -825,4 +895,168 @@
}
}
+ /**
+ * Create a network request for specified band in a network specifier.
+ */
+ private NetworkRequest createNetworkRequestForInternet(int band) {
+ final NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addTransportType(TRANSPORT_WIFI)
+ .setNetworkSpecifier(new WifiNetworkSpecifier.Builder()
+ .setBand(band).build())
+ .build();
+ return networkRequest;
+ }
+
+ /**
+ * Check if a wifi network info is as expected for multi internet connections.
+ * @return the WifiInfo of the network.
+ */
+ private WifiInfo checkWifiNetworkInfo(TestNetworkCallback testNetworkCallback,
+ int band) {
+ if (testNetworkCallback.networkCapabilities == null) {
+ return null;
+ }
+ WifiInfo wifiInfo = getWifiInfo(testNetworkCallback.networkCapabilities);
+ assertTrue(wifiInfo.isTrusted());
+ assertFalse(wifiInfo.isRestricted());
+ WifiInfo redact = wifiInfo
+ .makeCopy(NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION);
+ assertNotNull(wifiInfo.getInformationElements());
+ assertNull(redact.getInformationElements());
+ assertEquals(NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION
+ | NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS
+ | NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS,
+ redact.getApplicableRedactions());
+ assertEquals(band, getBandFromFrequency(wifiInfo.getFrequency()));
+
+ return wifiInfo;
+ }
+
+ /**
+ * Tests the entire connection success/failure flow using the provided suggestion.
+ *
+ * @param executorService Excutor service to run scan periodically (to trigger connection).
+ * @param expectConnectionSuccess Whether to expect connection success or not.
+ */
+ private void testMultiInternetConnectionFlowInternal(
+ @NonNull ScheduledExecutorService executorService,
+ boolean expectConnectionSuccess) throws Exception {
+ // File the network request & wait for the callback.
+ TestNetworkCallback testNetworkCallback2G = createTestNetworkCallback();
+ TestNetworkCallback testNetworkCallback5G = createTestNetworkCallback();
+ final NetworkRequest networkRequest2G = createNetworkRequestForInternet(
+ ScanResult.WIFI_BAND_24_GHZ);
+ final NetworkRequest networkRequest5G = createNetworkRequestForInternet(
+ ScanResult.WIFI_BAND_5_GHZ);
+ // Make sure wifi is connected to primary after wifi enabled with saved network.
+ PollingCheck.check("Wifi not connected", DURATION_NETWORK_CONNECTION_MILLIS,
+ () -> getNumWifiConnections() > 0);
+ try {
+ // Request both 2G and 5G wifi networks.
+ mConnectivityManager.requestNetwork(networkRequest2G, testNetworkCallback2G);
+ mConnectivityManager.requestNetwork(networkRequest5G, testNetworkCallback5G);
+ // Wait for the request to reach the wifi stack before kick-start periodic scans.
+ Thread.sleep(200);
+ boolean band2gFound = false;
+ boolean band5gFound = false;
+ // now wait for connection to complete and wait for callback
+ WifiInfo primaryInfo = null;
+ WifiInfo secondaryInfo = null;
+ if (testNetworkCallback2G.await(DURATION_NETWORK_CONNECTION_MILLIS)) {
+ WifiInfo info2g = checkWifiNetworkInfo(testNetworkCallback2G,
+ ScanResult.WIFI_BAND_24_GHZ);
+ if (info2g != null) {
+ if (info2g.isPrimary()) {
+ primaryInfo = info2g;
+ } else {
+ secondaryInfo = info2g;
+ }
+ band2gFound = true;
+ }
+ }
+ if (testNetworkCallback5G.await(DURATION_NETWORK_CONNECTION_MILLIS)) {
+ WifiInfo info5g = checkWifiNetworkInfo(testNetworkCallback5G,
+ ScanResult.WIFI_BAND_5_GHZ);
+ if (info5g != null) {
+ if (info5g.isPrimary()) {
+ primaryInfo = info5g;
+ } else {
+ secondaryInfo = info5g;
+ }
+ band5gFound = true;
+ }
+ }
+ if (expectConnectionSuccess) {
+ // Ensure both primary and non-primary networks are created.
+ assertTrue("Network not found on 2g", band2gFound);
+ assertTrue("Network not found on 5g", band5gFound);
+ assertFalse("Network unavailable on 2g", testNetworkCallback2G.onUnavailableCalled);
+ assertFalse("Network unavailable on 5g", testNetworkCallback5G.onUnavailableCalled);
+ assertNotNull("No primary network info", primaryInfo);
+ assertNotNull("No secondary network info", secondaryInfo);
+ assertFalse("Primary and secondary networks are same",
+ primaryInfo.equals(secondaryInfo));
+ // Ensure that there are 2 wifi connections available for apps.
+ assertEquals("Expecting 2 Wifi networks", 2, getNumWifiConnections());
+ // Check if the networks meets the expected requested multi internet state
+ int mode = mWifiManager.getStaConcurrencyForMultiInternetMode();
+ if (mode == mWifiManager.WIFI_MULTI_INTERNET_MODE_MULTI_AP) {
+ // Multi AP state allows connecting to same network or multi APs in other
+ // networks, with different BSSIDs.
+ assertFalse("Can not connect to same bssid" + primaryInfo.getBSSID()
+ + " / " + secondaryInfo.getBSSID(),
+ TextUtils.equals(primaryInfo.getBSSID(), secondaryInfo.getBSSID()));
+ } else if (mode == mWifiManager.WIFI_MULTI_INTERNET_MODE_DBS_AP) {
+ assertTrue("NETWORK_DBS mode can only connect to the same SSID but got "
+ + primaryInfo.getSSID() + " / " + secondaryInfo.getSSID(),
+ TextUtils.equals(primaryInfo.getSSID(), secondaryInfo.getSSID()));
+ assertEquals("NETWORK_DBS mode can only connect to the same network Id but got"
+ + primaryInfo.getNetworkId() + " / " + secondaryInfo.getNetworkId(),
+ primaryInfo.getNetworkId(), secondaryInfo.getNetworkId());
+ assertEquals("NETWORK_DBS mode can only connect to same security type but got"
+ + primaryInfo.getCurrentSecurityType() + " / "
+ + secondaryInfo.getCurrentSecurityType(),
+ primaryInfo.getCurrentSecurityType(),
+ secondaryInfo.getCurrentSecurityType());
+ } else {
+ fail("Invalid multi internet mode " + mode);
+ }
+ } else {
+ // Ensure no band specified wifi connection is created.
+ assertTrue(testNetworkCallback2G.onUnavailableCalled
+ || testNetworkCallback5G.onUnavailableCalled);
+ // Only one wifi network
+ assertEquals("There should be only one wifi network but got "
+ + getNumWifiConnections(), 1, getNumWifiConnections());
+ }
+ } finally {
+ mConnectivityManager.unregisterNetworkCallback(testNetworkCallback2G);
+ mConnectivityManager.unregisterNetworkCallback(testNetworkCallback5G);
+ executorService.shutdown();
+ }
+ }
+
+ /**
+ * Tests the entire connection success flow using the provided suggestion.
+ *
+ * Note: The caller needs to invoke this after acquiring shell identity.
+ *
+ * @param executorService Excutor service to run scan periodically (to trigger connection).
+ * @param expectConnectionSuccess Whether to expect connection success or not.
+ */
+ public void testMultiInternetConnectionFlowWithShellIdentity(
+ @NonNull ScheduledExecutorService executorService,
+ boolean expectConnectionSuccess) throws Exception {
+ final UiAutomation uiAutomation =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity(NETWORK_SETTINGS, CONNECTIVITY_INTERNAL);
+ testMultiInternetConnectionFlowInternal(
+ executorService, expectConnectionSuccess);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java
index 3bd8b28..8f76791 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java
@@ -16,6 +16,10 @@
package android.net.wifi.cts;
+import static android.net.wifi.WifiConfiguration.RANDOMIZATION_AUTO;
+import static android.net.wifi.WifiConfiguration.RANDOMIZATION_NONE;
+import static android.net.wifi.WifiConfiguration.RANDOMIZATION_NON_PERSISTENT;
+import static android.net.wifi.WifiConfiguration.RANDOMIZATION_PERSISTENT;
import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP;
import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B;
import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE;
@@ -140,4 +144,20 @@
configuration.setDeletionPriority(1);
assertEquals(1, configuration.getDeletionPriority());
}
-}
+
+ public void testSetGetMacRandomizationSetting() throws Exception {
+ WifiConfiguration configuration = new WifiConfiguration();
+
+ configuration.setMacRandomizationSetting(RANDOMIZATION_NONE);
+ assertEquals(RANDOMIZATION_NONE, configuration.getMacRandomizationSetting());
+
+ configuration.setMacRandomizationSetting(RANDOMIZATION_PERSISTENT);
+ assertEquals(RANDOMIZATION_PERSISTENT, configuration.getMacRandomizationSetting());
+
+ configuration.setMacRandomizationSetting(RANDOMIZATION_NON_PERSISTENT);
+ assertEquals(RANDOMIZATION_NON_PERSISTENT, configuration.getMacRandomizationSetting());
+
+ configuration.setMacRandomizationSetting(RANDOMIZATION_AUTO);
+ assertEquals(RANDOMIZATION_AUTO, configuration.getMacRandomizationSetting());
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
index a9402ba..16cc8a0 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
@@ -1009,4 +1009,37 @@
config.setDecoratedIdentityPrefix(TEST_DECORATED_IDENTITY_PREFIX);
assertEquals(TEST_DECORATED_IDENTITY_PREFIX, config.getDecoratedIdentityPrefix());
}
+
+ // TODO(b/196180536): Wait for T SDK finalization before changing
+ // to `@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)`
+ @SdkSuppress(minSdkVersion = 31)
+ public void testSetGetTrustOnFirstUse() {
+ if (!hasWifi()) {
+ return;
+ }
+ WifiEnterpriseConfig config = new WifiEnterpriseConfig();
+ assertFalse(config.isTrustOnFirstUseEnabled());
+ config.enableTrustOnFirstUse(true);
+ assertTrue(config.isTrustOnFirstUseEnabled());
+ config.enableTrustOnFirstUse(false);
+ assertFalse(config.isTrustOnFirstUseEnabled());
+ }
+
+ // TODO(b/196180536): Wait for T SDK finalization before changing
+ // to `@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)`
+ @SdkSuppress(minSdkVersion = 31)
+ public void testHasCaCertificate() {
+ if (!hasWifi()) {
+ return;
+ }
+ WifiEnterpriseConfig config = new WifiEnterpriseConfig();
+ assertFalse(config.hasCaCertificate());
+ config.setCaPath("/tmp/ca.cert");
+ assertTrue(config.hasCaCertificate());
+
+ config = new WifiEnterpriseConfig();
+ assertFalse(config.hasCaCertificate());
+ config.setCaCertificate(FakeKeys.CA_CERT0);
+ assertTrue(config.hasCaCertificate());
+ }
}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiJUnit3TestBase.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiJUnit3TestBase.java
index d884a50..baa266b 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiJUnit3TestBase.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiJUnit3TestBase.java
@@ -49,8 +49,9 @@
@Override
protected void tearDown() throws Exception {
if (mWasLocationEnabledForTest) {
- mLocationManager.setLocationEnabledForUser(
- false, UserHandle.getUserHandleForUid(Process.myUid()));
+ ShellIdentityUtils.invokeWithShellPermissions(() ->
+ mLocationManager.setLocationEnabledForUser(
+ false, UserHandle.getUserHandleForUid(Process.myUid())));
}
super.tearDown();
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiJUnit4TestBase.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiJUnit4TestBase.java
index 6bc6885..13fa031 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiJUnit4TestBase.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiJUnit4TestBase.java
@@ -55,8 +55,9 @@
@After
public void disableLocationIfOriginallyDisabled() throws Exception {
if (mWasLocationEnabledForTest) {
- mLocationManager.setLocationEnabledForUser(
- false, UserHandle.getUserHandleForUid(Process.myUid()));
+ ShellIdentityUtils.invokeWithShellPermissions(() ->
+ mLocationManager.setLocationEnabledForUser(
+ false, UserHandle.getUserHandleForUid(Process.myUid())));
}
}
}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
index a8dfee4..668dcd1 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -63,6 +63,8 @@
import android.net.wifi.WifiManager.WifiLock;
import android.net.wifi.WifiNetworkConnectionStatistics;
import android.net.wifi.WifiNetworkSuggestion;
+import android.net.wifi.WifiScanner;
+import android.net.wifi.WifiSsid;
import android.net.wifi.hotspot2.ConfigParser;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
@@ -510,7 +512,7 @@
fail("Please enable location for this test - since Marshmallow WiFi scan results are"
+ " empty when location is disabled!");
}
- runWithScanningEnabled(() -> {
+ runWithScanning(() -> {
setWifiEnabled(false);
Thread.sleep(TEST_WAIT_DURATION_MS);
startScan();
@@ -525,7 +527,7 @@
final String TAG = "Test";
assertNotNull(mWifiManager.createWifiLock(TAG));
assertNotNull(mWifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG));
- });
+ }, true /* run with enabled*/);
}
/**
@@ -988,18 +990,18 @@
}
TestExecutor executor = new TestExecutor();
- TestSoftApCallback capabilityCallback = new TestSoftApCallback(mLock);
+ TestSoftApCallback lohsSoftApCallback = new TestSoftApCallback(mLock);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
List<Integer> supportedSoftApBands = new ArrayList<>();
try {
uiAutomation.adoptShellPermissionIdentity();
- verifyRegisterSoftApCallback(executor, capabilityCallback);
+ verifyLohsRegisterSoftApCallback(executor, lohsSoftApCallback);
supportedSoftApBands = getSupportedSoftApBand(
- capabilityCallback.getCurrentSoftApCapability());
+ lohsSoftApCallback.getCurrentSoftApCapability());
} catch (Exception ex) {
} finally {
// clean up
- mWifiManager.unregisterSoftApCallback(capabilityCallback);
+ mWifiManager.unregisterLocalOnlyHotspotSoftApCallback(lohsSoftApCallback);
uiAutomation.dropShellPermissionIdentity();
}
TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
@@ -1148,6 +1150,38 @@
}
/**
+ * Verify setting the scan schedule.
+ */
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ public void testSetScreenOnScanSchedule() {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.setScreenOnScanSchedule(new int[] {20, 40}, new int[] {
+ WifiScanner.SCAN_TYPE_HIGH_ACCURACY, WifiScanner.SCAN_TYPE_LOW_LATENCY}));
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.setScreenOnScanSchedule(null, null));
+ }
+
+ /**
+ * Verify a normal app cannot set the scan schedule.
+ */
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ public void testSetScreenOnScanScheduleNoPermission() {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ try {
+ mWifiManager.setScreenOnScanSchedule(null, null);
+ fail("A normal app should not be able to call this API.");
+ } catch (SecurityException e) {
+ }
+ }
+
+ /**
* Test coverage for the constructor of AddNetworkResult.
*/
public void testAddNetworkResultCreation() {
@@ -1168,6 +1202,39 @@
}
/**
+ * Verify {@link WifiManager#setSsidsDoNotBlocklist(Set)} can be called with sufficient
+ * privilege.
+ */
+ public void testGetAndSetSsidsDoNotBlocklist() {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ Set<WifiSsid> ssids = new ArraySet<>();
+ ssids.add(WifiSsid.fromString("\"TEST_SSID_1\""));
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.setSsidsDoNotBlocklist(ssids));
+
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> assertEquals("Ssids should match", ssids,
+ mWifiManager.getSsidsDoNotBlocklist()));
+
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.setSsidsDoNotBlocklist(Collections.EMPTY_SET));
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> assertEquals("Should equal to empty set",
+ Collections.EMPTY_SET,
+ mWifiManager.getSsidsDoNotBlocklist()));
+
+ try {
+ mWifiManager.setSsidsDoNotBlocklist(Collections.EMPTY_SET);
+ fail("Expected SecurityException when called without permission");
+ } catch (SecurityException e) {
+ // expect the exception
+ }
+ }
+
+ /**
* Verify that {@link WifiManager#addNetworkPrivileged(WifiConfiguration)} throws a
* SecurityException when called by a normal app.
*/
@@ -1214,6 +1281,36 @@
}
/**
+ * Verify {@link WifiManager#getPrivilegedConnectedNetwork()} returns the currently
+ * connected WifiConfiguration with randomized MAC address filtered out.
+ */
+ public void testGetPrivilegedConnectedNetworkSuccess() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ setWifiEnabled(true);
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ mWifiManager.startScan();
+ waitForConnection(); // ensures that there is at-least 1 saved network on the device.
+
+ WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+ int curNetworkId = wifiInfo.getNetworkId();
+ assertNotEquals("Should be connected to valid networkId", INVALID_NETWORK_ID,
+ curNetworkId);
+ WifiConfiguration curConfig = mWifiManager.getPrivilegedConnectedNetwork();
+ assertEquals("NetworkId should match", curNetworkId, curConfig.networkId);
+ assertEquals("SSID should match", wifiInfo.getSSID(), curConfig.SSID);
+ assertEquals("Randomized MAC should be filtered out", WifiInfo.DEFAULT_MAC_ADDRESS,
+ curConfig.getRandomizedMacAddress().toString());
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ /**
* Verify {@link WifiManager#addNetworkPrivileged(WifiConfiguration)} works properly when the
* calling app has permissions.
*/
@@ -1332,14 +1429,15 @@
// Skip the test if wifi module version is older than S.
return;
}
- List<WifiConfiguration> testConfigs = new ArrayList<>();
- testConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OPEN));
- testConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OWE));
- testConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_PSK));
- testConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_SAE));
- testConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
+ List<WifiConfiguration> baseConfigs = new ArrayList<>();
+ baseConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OPEN));
+ baseConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_PSK));
+ baseConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
WifiConfiguration.SECURITY_TYPE_EAP));
- testConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
+ List<WifiConfiguration> upgradeConfigs = new ArrayList<>();
+ upgradeConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OWE));
+ upgradeConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_SAE));
+ upgradeConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
@@ -1349,46 +1447,51 @@
mWifiManager.getPrivilegedConfiguredNetworks().size();
final int originalCallerConfiguredNetworksNumber =
mWifiManager.getCallerConfiguredNetworks().size();
- for (WifiConfiguration c: testConfigs) {
+ for (WifiConfiguration c: baseConfigs) {
WifiManager.AddNetworkResult result = mWifiManager.addNetworkPrivileged(c);
assertEquals(WifiManager.AddNetworkResult.STATUS_SUCCESS, result.statusCode);
assertTrue(result.networkId >= 0);
c.networkId = result.networkId;
}
- List<WifiConfiguration> expectedConfigs = testConfigs;
+ for (WifiConfiguration c: upgradeConfigs) {
+ WifiManager.AddNetworkResult result = mWifiManager.addNetworkPrivileged(c);
+ assertEquals(WifiManager.AddNetworkResult.STATUS_SUCCESS, result.statusCode);
+ assertTrue(result.networkId >= 0);
+ c.networkId = result.networkId;
+ }
+ // open/owe, psk/sae, and wpa2e/wpa3e should be merged
+ // so they should have the same network ID.
+ for (int i = 0; i < baseConfigs.size(); i++) {
+ assertEquals(baseConfigs.get(i).networkId, upgradeConfigs.get(i).networkId);
+ }
+
+ int numAddedConfigs = baseConfigs.size();
+ List<WifiConfiguration> expectedConfigs = new ArrayList<>(baseConfigs);
if (SdkLevel.isAtLeastS()) {
- // open/owe, psk/sae, and wpa2e/wpa3e should be merged
- // so they should have the same network ID.
- assertEquals(testConfigs.get(0).networkId, testConfigs.get(1).networkId);
- assertEquals(testConfigs.get(2).networkId, testConfigs.get(3).networkId);
- assertEquals(testConfigs.get(4).networkId, testConfigs.get(5).networkId);
- } else {
- // Network IDs for different security types should be unique for R
- assertNotEquals(testConfigs.get(0).networkId, testConfigs.get(1).networkId);
- assertNotEquals(testConfigs.get(2).networkId, testConfigs.get(3).networkId);
- assertNotEquals(testConfigs.get(4).networkId, testConfigs.get(5).networkId);
- // WPA3-Enterprise is omitted when WPA2-Enterprise is present for R
- expectedConfigs = testConfigs.subList(0, 5);
+ // S devices and above will return one additional config per each security type
+ // added, so we include the number of both base and upgrade configs.
+ numAddedConfigs += upgradeConfigs.size();
+ expectedConfigs.addAll(upgradeConfigs);
}
List<WifiConfiguration> configuredNetworks = mWifiManager.getConfiguredNetworks();
- assertEquals(originalConfiguredNetworksNumber + expectedConfigs.size(),
+ assertEquals(originalConfiguredNetworksNumber + numAddedConfigs,
configuredNetworks.size());
assertConfigsAreFound(expectedConfigs, configuredNetworks);
List<WifiConfiguration> privilegedConfiguredNetworks =
mWifiManager.getPrivilegedConfiguredNetworks();
- assertEquals(originalPrivilegedConfiguredNetworksNumber + expectedConfigs.size(),
+ assertEquals(originalPrivilegedConfiguredNetworksNumber + numAddedConfigs,
privilegedConfiguredNetworks.size());
assertConfigsAreFound(expectedConfigs, privilegedConfiguredNetworks);
List<WifiConfiguration> callerConfiguredNetworks =
mWifiManager.getCallerConfiguredNetworks();
- assertEquals(originalCallerConfiguredNetworksNumber + expectedConfigs.size(),
+ assertEquals(originalCallerConfiguredNetworksNumber + numAddedConfigs,
callerConfiguredNetworks.size());
assertConfigsAreFound(expectedConfigs, callerConfiguredNetworks);
} finally {
- for (WifiConfiguration c: testConfigs) {
+ for (WifiConfiguration c: baseConfigs) {
if (c.networkId >= 0) {
mWifiManager.removeNetwork(c.networkId);
}
@@ -1463,6 +1566,73 @@
}
}
+ private SoftApConfiguration.Builder generateSoftApConfigBuilderWithSsid(String ssid) {
+ if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+ return new SoftApConfiguration.Builder().setWifiSsid(WifiSsid.fromUtf8Text(ssid));
+ }
+ return new SoftApConfiguration.Builder().setSsid(ssid);
+ }
+
+ private void assertSsidEquals(SoftApConfiguration config, String expectedSsid) {
+ if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+ assertEquals(expectedSsid, config.getWifiSsid().getUtf8Text());
+ } else {
+ assertEquals(expectedSsid, config.getSsid());
+ }
+ }
+
+ public void testStartLocalOnlyHotspotWithSupportedBand() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+
+ // check that softap mode is supported by the device
+ if (!mWifiManager.isPortableHotspotSupported()) {
+ return;
+ }
+
+ TestExecutor executor = new TestExecutor();
+ TestSoftApCallback lohsSoftApCallback = new TestSoftApCallback(mLock);
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ boolean wifiEnabled = mWifiManager.isWifiEnabled();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ verifyLohsRegisterSoftApCallback(executor, lohsSoftApCallback);
+ SoftApConfiguration.Builder customConfigBuilder =
+ generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
+ .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
+
+ SparseIntArray testBandsAndChannels = getAvailableBandAndChannelForTesting(
+ lohsSoftApCallback.getCurrentSoftApCapability());
+
+ for (int i = 0; i < testBandsAndChannels.size(); i++) {
+ TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
+ int testBand = testBandsAndChannels.keyAt(i);
+ customConfigBuilder.setBand(testBand);
+ mWifiManager.startLocalOnlyHotspot(customConfigBuilder.build(), executor, callback);
+ // now wait for callback
+ Thread.sleep(TEST_WAIT_DURATION_MS);
+
+ // Verify callback is run on the supplied executor
+ assertFalse(callback.onStartedCalled);
+ executor.runAll();
+ assertTrue(callback.onStartedCalled);
+ assertNotNull(callback.reservation);
+ SoftApConfiguration softApConfig = callback.reservation.getSoftApConfiguration();
+ assertEquals(TEST_SSID_UNQUOTED, softApConfig.getWifiSsid().getUtf8Text());
+ assertEquals(TEST_PASSPHRASE, softApConfig.getPassphrase());
+ assertEquals(testBand, softApConfig.getBand());
+ assertTrue(lohsSoftApCallback.getCurrentSoftApInfo().getFrequency() > 0);
+ stopLocalOnlyHotspot(callback, wifiEnabled);
+ }
+ } finally {
+ // clean up
+ mWifiManager.unregisterSoftApCallback(lohsSoftApCallback);
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
public void testStartLocalOnlyHotspotWithConfigBssid() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
@@ -1475,17 +1645,17 @@
TestExecutor executor = new TestExecutor();
TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
- TestSoftApCallback capabilityCallback = new TestSoftApCallback(mLock);
+ TestSoftApCallback lohsSoftApCallback = new TestSoftApCallback(mLock);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
boolean wifiEnabled = mWifiManager.isWifiEnabled();
try {
uiAutomation.adoptShellPermissionIdentity();
- verifyRegisterSoftApCallback(executor, capabilityCallback);
- SoftApConfiguration.Builder customConfigBuilder = new SoftApConfiguration.Builder()
- .setSsid(TEST_SSID_UNQUOTED)
+ verifyLohsRegisterSoftApCallback(executor, lohsSoftApCallback);
+ SoftApConfiguration.Builder customConfigBuilder =
+ generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
.setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
- boolean isSupportCustomizedMac = capabilityCallback.getCurrentSoftApCapability()
+ boolean isSupportCustomizedMac = lohsSoftApCallback.getCurrentSoftApCapability()
.areFeaturesSupported(
SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION)
&& PropertyUtil.isVndkApiLevelNewerThan(Build.VERSION_CODES.S);
@@ -1509,12 +1679,12 @@
if (isSupportCustomizedMac) {
assertEquals(TEST_MAC, softApConfig.getBssid());
}
- assertEquals(TEST_SSID_UNQUOTED, softApConfig.getSsid());
+ assertSsidEquals(softApConfig, TEST_SSID_UNQUOTED);
assertEquals(TEST_PASSPHRASE, softApConfig.getPassphrase());
} finally {
// clean up
stopLocalOnlyHotspot(callback, wifiEnabled);
- mWifiManager.unregisterSoftApCallback(capabilityCallback);
+ mWifiManager.unregisterLocalOnlyHotspotSoftApCallback(lohsSoftApCallback);
uiAutomation.dropShellPermissionIdentity();
}
}
@@ -1528,10 +1698,11 @@
if (!mWifiManager.isPortableHotspotSupported()) {
return;
}
- SoftApConfiguration customConfig = new SoftApConfiguration.Builder()
- .setSsid(TEST_SSID_UNQUOTED)
+ SoftApConfiguration customConfig =
+ generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
.setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
.build();
+
TestExecutor executor = new TestExecutor();
TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -1551,7 +1722,7 @@
assertNotNull(callback.reservation);
SoftApConfiguration softApConfig = callback.reservation.getSoftApConfiguration();
assertNotNull(softApConfig);
- assertEquals(TEST_SSID_UNQUOTED, softApConfig.getSsid());
+ assertSsidEquals(softApConfig, TEST_SSID_UNQUOTED);
assertEquals(TEST_PASSPHRASE, softApConfig.getPassphrase());
} finally {
// clean up
@@ -1924,19 +2095,19 @@
}
}
- private void runWithScanningEnabled(ThrowingRunnable r) throws Exception {
- boolean wasScanEnabledForTest = false;
- if (!mWifiManager.isScanAlwaysAvailable()) {
+ private void runWithScanning(ThrowingRunnable r, boolean isEnabled) throws Exception {
+ boolean scanModeChangedForTest = false;
+ if (mWifiManager.isScanAlwaysAvailable() != isEnabled) {
ShellIdentityUtils.invokeWithShellPermissions(
- () -> mWifiManager.setScanAlwaysAvailable(true));
- wasScanEnabledForTest = true;
+ () -> mWifiManager.setScanAlwaysAvailable(isEnabled));
+ scanModeChangedForTest = true;
}
try {
r.run();
} finally {
- if (wasScanEnabledForTest) {
+ if (scanModeChangedForTest) {
ShellIdentityUtils.invokeWithShellPermissions(
- () -> mWifiManager.setScanAlwaysAvailable(false));
+ () -> mWifiManager.setScanAlwaysAvailable(!isEnabled));
}
}
}
@@ -1964,7 +2135,7 @@
fail("Please enable location for this test - since Marshmallow WiFi scan results are"
+ " empty when location is disabled!");
}
- runWithScanningEnabled(() -> {
+ runWithScanning(() -> {
setWifiEnabled(false);
turnScreenOn();
assertWifiScanningIsOn();
@@ -1973,7 +2144,7 @@
assertWifiScanningIsOn();
turnScreenOn();
assertWifiScanningIsOn();
- });
+ }, true /* run with enabled*/);
}
/**
@@ -1998,7 +2169,7 @@
fail("Please enable location for this test - since Marshmallow WiFi scan results are"
+ " empty when location is disabled!");
}
- runWithScanningEnabled(() -> {
+ runWithScanning(() -> {
setWifiEnabled(true);
turnScreenOn();
assertWifiScanningIsOn();
@@ -2007,7 +2178,7 @@
assertWifiScanningIsOn();
turnScreenOn();
assertWifiScanningIsOn();
- });
+ }, true /* run with enabled*/);
}
/**
@@ -2032,6 +2203,22 @@
() -> {
executor.runAll();
// Verify callback is run on the supplied executor and called
+ return callback.getOnStateChangedCalled()
+ && callback.getOnSoftapInfoChangedCalledCount() > 0
+ && callback.getOnSoftApCapabilityChangedCalled()
+ && callback.getOnConnectedClientCalled();
+ });
+ }
+
+ private void verifyLohsRegisterSoftApCallback(TestExecutor executor,
+ TestSoftApCallback callback) throws Exception {
+ // Register callback to get SoftApCapability
+ mWifiManager.registerLocalOnlyHotspotSoftApCallback(executor, callback);
+ PollingCheck.check(
+ "SoftAp register failed!", 5_000,
+ () -> {
+ executor.runAll();
+ // Verify callback is run on the supplied executor and called
return callback.getOnStateChangedCalled() &&
callback.getOnSoftapInfoChangedCalledCount() > 0 &&
callback.getOnSoftApCapabilityChangedCalled() &&
@@ -2047,10 +2234,23 @@
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
assertTrue(currentConfig.isUserConfiguration());
}
+
+ if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+ // Verify set/get with the deprecated set/getSsid()
+ SoftApConfiguration oldSsidConfig = new SoftApConfiguration.Builder(targetConfig)
+ .setWifiSsid(null)
+ .setSsid(targetConfig.getSsid()).build();
+ mWifiManager.setSoftApConfiguration(oldSsidConfig);
+ currentConfig = mWifiManager.getSoftApConfiguration();
+ compareSoftApConfiguration(oldSsidConfig, currentConfig);
+ }
}
private void compareSoftApConfiguration(SoftApConfiguration currentConfig,
SoftApConfiguration testSoftApConfig) {
+ if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+ assertEquals(currentConfig.getWifiSsid(), testSoftApConfig.getWifiSsid());
+ }
assertEquals(currentConfig.getSsid(), testSoftApConfig.getSsid());
assertEquals(currentConfig.getBssid(), testSoftApConfig.getBssid());
assertEquals(currentConfig.getSecurityType(), testSoftApConfig.getSecurityType());
@@ -2079,6 +2279,10 @@
testSoftApConfig.isBridgedModeOpportunisticShutdownEnabled());
assertEquals(currentConfig.isIeee80211axEnabled(),
testSoftApConfig.isIeee80211axEnabled());
+ if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+ assertEquals(currentConfig.getBridgedModeOpportunisticShutdownTimeoutMillis(),
+ testSoftApConfig.getBridgedModeOpportunisticShutdownTimeoutMillis());
+ }
}
}
@@ -2096,7 +2300,6 @@
PollingCheck.check(
"SoftAp turn off failed!", 2_000,
() -> mWifiManager.isWifiApEnabled() == false);
- mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
}
}
@@ -2175,11 +2378,12 @@
int[] expectedBands = {SoftApConfiguration.BAND_2GHZ,
SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ};
// Test bridged SoftApConfiguration set and get (setBands)
- SoftApConfiguration testSoftApConfig = new SoftApConfiguration.Builder()
- .setSsid(TEST_SSID_UNQUOTED)
+ SoftApConfiguration testSoftApConfig =
+ generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
.setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
.setBands(expectedBands)
.build();
+
boolean shouldFallbackToSingleAp = shouldFallbackToSingleAp(testBands,
callback.getCurrentSoftApCapability());
verifySetGetSoftApConfig(testSoftApConfig);
@@ -2242,8 +2446,8 @@
dual_channels.put(SoftApConfiguration.BAND_5GHZ,
callback.getCurrentSoftApCapability()
.getSupportedChannelList(SoftApConfiguration.BAND_5GHZ)[0]);
- SoftApConfiguration testSoftApConfig = new SoftApConfiguration.Builder()
- .setSsid(TEST_SSID_UNQUOTED)
+ SoftApConfiguration testSoftApConfig =
+ generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
.setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
.setChannels(dual_channels)
.build();
@@ -2294,8 +2498,8 @@
turnOffWifiAndTetheredHotspotIfEnabled();
verifyRegisterSoftApCallback(executor, callback);
- SoftApConfiguration.Builder softApConfigBuilder = new SoftApConfiguration.Builder()
- .setSsid(TEST_SSID_UNQUOTED)
+ SoftApConfiguration.Builder softApConfigBuilder =
+ generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
.setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
.setAutoShutdownEnabled(true)
.setShutdownTimeoutMillis(100000)
@@ -2303,6 +2507,10 @@
callback.getCurrentSoftApCapability()).keyAt(0))
.setHiddenSsid(false);
+ if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+ softApConfigBuilder.setBridgedModeOpportunisticShutdownTimeoutMillis(30_000);
+ }
+
// Test SoftApConfiguration set and get
verifySetGetSoftApConfig(softApConfigBuilder.build());
@@ -2400,8 +2608,8 @@
SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION)
&& PropertyUtil.isVndkApiLevelNewerThan(Build.VERSION_CODES.S);
- SoftApConfiguration.Builder testSoftApConfigBuilder = new SoftApConfiguration.Builder()
- .setSsid(TEST_SSID_UNQUOTED)
+ SoftApConfiguration.Builder testSoftApConfigBuilder =
+ generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
.setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
.setChannel(testBandsAndChannels.valueAt(0), testBandsAndChannels.keyAt(0));
@@ -2542,17 +2750,8 @@
assertTrue(actionListener.onSuccessCalled);
// Wait for connection to complete & ensure we are connected to the saved network.
waitForConnection();
- if (SdkLevel.isAtLeastS()) {
- assertEquals(savedNetworkToConnect.networkId,
- mWifiManager.getConnectionInfo().getNetworkId());
- } else {
- // In R, auto-upgraded network IDs may be different from the original saved network.
- // Since we may end up selecting the auto-upgraded network ID for connection and end
- // up connected to the original saved network with a different network ID, we should
- // instead match by SSID.
- assertEquals(savedNetworkToConnect.SSID,
- mWifiManager.getConnectionInfo().getSSID());
- }
+ assertEquals(savedNetworkToConnect.networkId,
+ mWifiManager.getConnectionInfo().getNetworkId());
} finally {
// Re-enable all saved networks before exiting.
if (savedNetworks != null) {
@@ -3302,6 +3501,110 @@
waitForConnection();
}
+ private class TestActiveCountryCodeChangedCallback implements
+ WifiManager.ActiveCountryCodeChangedCallback {
+ private String mCurrentCountryCode;
+ private boolean mIsOnActiveCountryCodeChangedCalled = false;
+ private boolean mIsOnCountryCodeInactiveCalled = false;
+
+ public boolean isOnActiveCountryCodeChangedCalled() {
+ return mIsOnActiveCountryCodeChangedCalled;
+ }
+
+ public boolean isOnCountryCodeInactiveCalled() {
+ return mIsOnCountryCodeInactiveCalled;
+ }
+ public void resetCallbackCallededHistory() {
+ mIsOnActiveCountryCodeChangedCalled = false;
+ mIsOnCountryCodeInactiveCalled = false;
+ }
+
+ public String getCurrentDriverCountryCode() {
+ return mCurrentCountryCode;
+ }
+
+ @Override
+ public void onActiveCountryCodeChanged(String country) {
+ Log.d(TAG, "Receive DriverCountryCodeChanged to " + country);
+ mCurrentCountryCode = country;
+ mIsOnActiveCountryCodeChangedCalled = true;
+ }
+
+ @Override
+ public void onCountryCodeInactive() {
+ mCurrentCountryCode = null;
+ mIsOnCountryCodeInactiveCalled = true;
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public void testActiveCountryCodeChangedCallback() throws Exception {
+ TestActiveCountryCodeChangedCallback testCountryCodeChangedCallback =
+ new TestActiveCountryCodeChangedCallback();
+ TestExecutor executor = new TestExecutor();
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ turnOffWifiAndTetheredHotspotIfEnabled();
+ // Run with scanning disable to make sure there is no active mode.
+ runWithScanning(() -> {
+ mWifiManager.registerActiveCountryCodeChangedCallback(
+ executor, testCountryCodeChangedCallback);
+
+
+ PollingCheck.check(
+ "DriverCountryCode is non-null when wifi off",
+ 5000,
+ () -> {
+ executor.runAll();
+ return testCountryCodeChangedCallback
+ .isOnActiveCountryCodeChangedCalled()
+ && testCountryCodeChangedCallback.getCurrentDriverCountryCode()
+ == null;
+ });
+ // Enable wifi to make sure country code has been updated.
+ setWifiEnabled(true);
+ PollingCheck.check(
+ "DriverCountryCode is null when wifi on",
+ 5000,
+ () -> {
+ executor.runAll();
+ return testCountryCodeChangedCallback
+ .isOnActiveCountryCodeChangedCalled()
+ && testCountryCodeChangedCallback.getCurrentDriverCountryCode()
+ != null;
+ });
+ // Disable wifi to trigger country code change
+ setWifiEnabled(false);
+ PollingCheck.check(
+ "DriverCountryCode should be null when wifi off",
+ 5000,
+ () -> {
+ executor.runAll();
+ return testCountryCodeChangedCallback.isOnCountryCodeInactiveCalled()
+ && testCountryCodeChangedCallback
+ .getCurrentDriverCountryCode() == null;
+ });
+ mWifiManager.unregisterActiveCountryCodeChangedCallback(
+ testCountryCodeChangedCallback);
+ testCountryCodeChangedCallback.resetCallbackCallededHistory();
+ setWifiEnabled(true);
+ // Check there is no callback has been called.
+ PollingCheck.check(
+ "Callback is called after unregister",
+ 5000,
+ () -> {
+ executor.runAll();
+ return !testCountryCodeChangedCallback.isOnCountryCodeInactiveCalled()
+ && !testCountryCodeChangedCallback
+ .isOnActiveCountryCodeChangedCalled();
+ });
+ }, false /* Run with disabled */);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
/**
* Test that the wifi country code is either null, or a length-2 string.
*/
@@ -3775,7 +4078,7 @@
assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
mWifiManager.addNetworkSuggestions(Arrays.asList(suggestion)));
verifySuggestionFoundWithMacRandomizationSetting(TEST_SSID,
- WifiConfiguration.RANDOMIZATION_NON_PERSISTENT);
+ WifiNetworkSuggestion.RANDOMIZATION_NON_PERSISTENT);
suggestion = new WifiNetworkSuggestion.Builder()
.setSsid(TEST_SSID).setWpa2Passphrase(TEST_PASSPHRASE)
@@ -3783,7 +4086,7 @@
assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
mWifiManager.addNetworkSuggestions(Arrays.asList(suggestion)));
verifySuggestionFoundWithMacRandomizationSetting(TEST_SSID,
- WifiConfiguration.RANDOMIZATION_PERSISTENT);
+ WifiNetworkSuggestion.RANDOMIZATION_PERSISTENT);
}
private void verifySuggestionFoundWithMacRandomizationSetting(String ssid,
@@ -3791,8 +4094,7 @@
List<WifiNetworkSuggestion> retrievedSuggestions = mWifiManager.getNetworkSuggestions();
for (WifiNetworkSuggestion entry : retrievedSuggestions) {
if (entry.getSsid().equals(ssid)) {
- assertEquals(macRandomizationSetting,
- entry.getWifiConfiguration().macRandomizationSetting);
+ assertEquals(macRandomizationSetting, entry.getMacRandomizationSetting());
return; // pass test after the MAC randomization setting is verified.
}
}
@@ -3994,6 +4296,10 @@
mWifiManager.startScan();
ensureNotConnected();
+ // verify null is returned when attempting to get current configured network.
+ WifiConfiguration config = mWifiManager.getPrivilegedConnectedNetwork();
+ assertNull("config should be null because wifi is not connected", config);
+
// Now enable autojoin on all networks.
mWifiManager.allowAutojoinGlobal(true);
@@ -4148,6 +4454,20 @@
}
}
+ /**
+ * Tests {@link WifiManager#isTrustOnFirstUseSupported()} does not crash.
+ */
+ // TODO(b/196180536): Wait for T SDK finalization before changing
+ // to `@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)`
+ @SdkSuppress(minSdkVersion = 31)
+ public void testIsTrustOnFirstUseSupported() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ mWifiManager.isTrustOnFirstUseSupported();
+ }
+
public class TestCoexCallback extends WifiManager.CoexCallback {
private Object mCoexLock;
private int mOnCoexUnsafeChannelChangedCount;
@@ -4391,6 +4711,60 @@
}
/**
+ * Tests {@link WifiManager#setWifiPasspointEnabled)} raise security exception without
+ * permission.
+ */
+ // TODO(b/139192273): Wait for T SDK finalization before changing
+ // to `@SdkSuppress(minSdkVersion = Build.VERSION_CODES.T)`
+ @SdkSuppress(minSdkVersion = 31)
+ public void testEnablePasspointWithoutPermission() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ try {
+ mWifiManager.setWifiPasspointEnabled(true);
+ fail("setWifiPasspointEnabled() expected to fail - privileged call");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Tests {@link WifiManager#setWifiPasspointEnabled)} does not crash and returns success.
+ */
+ // TODO(b/139192273): Wait for T SDK finalization before changing
+ // to `@SdkSuppress(minSdkVersion = Build.VERSION_CODES.T)`
+ @SdkSuppress(minSdkVersion = 31)
+ public void testEnablePasspoint() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+
+ // The below API only works with privileged permissions (obtained via shell identity
+ // for test)
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ // Check if passpoint is enabled by default.
+ assertTrue(mWifiManager.isWifiPasspointEnabled());
+ // Try to disable passpoint
+ mWifiManager.setWifiPasspointEnabled(false);
+ PollingCheck.check(
+ "Wifi passpoint turn off failed!", 2_000,
+ () -> mWifiManager.isWifiPasspointEnabled() == false);
+ // Try to enable passpoint
+ mWifiManager.setWifiPasspointEnabled(true);
+ PollingCheck.check(
+ "Wifi passpoint turn on failed!", 2_000,
+ () -> mWifiManager.isWifiPasspointEnabled() == true);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ /**
* Tests {@link WifiManager#isDecoratedIdentitySupported)} does not crash.
*/
public void testIsDecoratedIdentitySupported() throws Exception {
@@ -4570,4 +4944,73 @@
mBlocker.countDown();
}
}
+
+ /**
+ * Tests {@link WifiManager#setStaConcurrencyForMultiInternetMode)} raise security exception
+ * without permission.
+ */
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ public void testIsStaConcurrencyForMultiInternetSupported() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ // ensure no crash.
+ mWifiManager.isStaConcurrencyForMultiInternetSupported();
+ }
+
+ /**
+ * Tests {@link WifiManager#setStaConcurrencyForMultiInternetMode)} raise security exception
+ * without permission.
+ */
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ public void testSetStaConcurrencyForMultiInternetModeWithoutPermission() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())
+ || !mWifiManager.isStaConcurrencyForMultiInternetSupported()) {
+ // skip the test if WiFi is not supported or multi internet feature not supported.
+ return;
+ }
+ try {
+ mWifiManager.setStaConcurrencyForMultiInternetMode(
+ WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED);
+ fail("setWifiPasspointEnabled() expected to fail - privileged call");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Tests {@link WifiManager#setStaConcurrencyForMultiInternetMode)} does not crash.
+ */
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ public void testSetStaConcurrencyForMultiInternetMode() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())
+ || !mWifiManager.isStaConcurrencyForMultiInternetSupported()) {
+ // skip the test if WiFi is not supported or multi internet feature not supported.
+ return;
+ }
+
+ // The below API only works with privileged permissions (obtained via shell identity
+ // for test)
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ // Try to disable multi internet
+ mWifiManager.setStaConcurrencyForMultiInternetMode(
+ WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED);
+ PollingCheck.check(
+ "Wifi multi internet disable failed!", 2_000,
+ () -> mWifiManager.getStaConcurrencyForMultiInternetMode()
+ == WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED);
+ // Try to enable multi internet
+ mWifiManager.setStaConcurrencyForMultiInternetMode(
+ WifiManager.WIFI_MULTI_INTERNET_MODE_MULTI_AP);
+ PollingCheck.check(
+ "Wifi multi internet turn on failed!", 2_000,
+ () -> mWifiManager.getStaConcurrencyForMultiInternetMode()
+ == WifiManager.WIFI_MULTI_INTERNET_MODE_MULTI_AP);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
index 5e54e9b..7190952 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
@@ -23,7 +23,6 @@
import static android.os.Process.myUid;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -46,6 +45,7 @@
import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSp;
import android.os.Build;
+import android.os.ParcelUuid;
import android.platform.test.annotations.AppModeFull;
import android.support.test.uiautomator.UiDevice;
import android.telephony.TelephonyManager;
@@ -93,6 +93,11 @@
private static final int TEST_PRIORITY = 5;
private static final int TEST_PRIORITY_GROUP = 1;
private static final int TEST_SUB_ID = 1;
+ private static final ParcelUuid GROUP_UUID = ParcelUuid
+ .fromString("0000110B-0000-1000-8000-00805F9B34FB");
+ private static final int DURATION_NETWORK_DISCONNECT_MILLIS = 3_000;
+ private static final int DURATION_NETWORK_LINGER_MILLIS = 30_000;
+ private static final int DURATION_NETWORK_UPDATE = 10_000;
private static boolean sWasVerboseLoggingEnabled;
private static boolean sWasScanThrottleEnabled;
@@ -993,6 +998,20 @@
}
/**
+ * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class, with SubscriptionGroup
+ */
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ @Test
+ public void testBuilderWithSubscriptionGroup() throws Exception {
+ WifiNetworkSuggestion suggestion =
+ new WifiNetworkSuggestion.Builder()
+ .setSsid(TEST_SSID)
+ .setSubscriptionGroup(GROUP_UUID)
+ .build();
+ assertEquals(GROUP_UUID, suggestion.getSubscriptionGroup());
+ }
+
+ /**
* Helper function for creating a {@link PasspointConfiguration} for testing.
*
* @return {@link PasspointConfiguration}
@@ -1101,11 +1120,12 @@
}
/**
- * Connect to a network using suggestion API.
+ * Connect to a network using suggestion API then remove with
+ * {@link WifiManager#ACTION_REMOVE_SUGGESTION_DISCONNECT}
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
@Test
- public void testConnectToSuggestion() throws Exception {
+ public void testConnectToSuggestionThenRemoveWithImmediateDisconnect() throws Exception {
assertNotNull(sTestNetwork);
WifiNetworkSuggestion suggestion =
TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
@@ -1113,7 +1133,60 @@
.build();
sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
sTestNetwork, suggestion, mExecutorService,
- Set.of() /* restrictedNetworkCapability */);
+ Set.of()/* restrictedNetworkCapability */, false/* restrictedNetwork */);
+ TestHelper.TestNetworkCallback callback = (TestHelper.TestNetworkCallback)
+ sNsNetworkCallback;
+ while (callback.waitForAnyCallback(DURATION_NETWORK_UPDATE));
+ sWifiManager.removeNetworkSuggestions(List.of(suggestion),
+ WifiManager.ACTION_REMOVE_SUGGESTION_DISCONNECT);
+ callback.waitForAnyCallback(DURATION_NETWORK_DISCONNECT_MILLIS);
+ assertTrue(callback.onLostCalled);
+ }
+
+ /**
+ * Connect to a network using suggestion API, then remove with
+ * {@link WifiManager#ACTION_REMOVE_SUGGESTION_LINGER}
+ */
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+ @Test
+ public void testConnectToSuggestionThenRemoveWithLingering() throws Exception {
+ assertNotNull(sTestNetwork);
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ sTestNetwork)
+ .build();
+ sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
+ sTestNetwork, suggestion, mExecutorService,
+ Set.of()/* restrictedNetworkCapability */, false/* restrictedNetwork */);
+ TestHelper.TestNetworkCallback callback = (TestHelper.TestNetworkCallback)
+ sNsNetworkCallback;
+ while (callback.waitForAnyCallback(DURATION_NETWORK_UPDATE));
+ sWifiManager.removeNetworkSuggestions(List.of(suggestion),
+ WifiManager.ACTION_REMOVE_SUGGESTION_LINGER);
+ callback.waitForAnyCallback(DURATION_NETWORK_DISCONNECT_MILLIS);
+ // Should not disconnect immediately
+ assertFalse(callback.onLostCalled);
+ // After linger time out, should disconnect.
+ Thread.sleep(DURATION_NETWORK_LINGER_MILLIS);
+ assertTrue(callback.onLostCalled);
+ }
+
+ /**
+ * Connect to a restricted network using suggestion API.
+ */
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+ @Test
+ public void testConnectToRestrictedSuggestion() throws Exception {
+ assertNotNull(sTestNetwork);
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ sTestNetwork)
+ .setRestricted(true)
+ .build();
+ assertTrue(suggestion.isRestricted());
+ sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
+ sTestNetwork, suggestion, mExecutorService,
+ Set.of()/* restrictedNetworkCapability */, true);
}
/**
@@ -1129,7 +1202,7 @@
.setOemPaid(true)
.build();
sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
- sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PAID));
+ sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PAID), false);
}
/**
@@ -1147,7 +1220,7 @@
.build();
sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
sTestNetwork, suggestion, mExecutorService,
- Set.of(NET_CAPABILITY_OEM_PAID, NET_CAPABILITY_OEM_PRIVATE));
+ Set.of(NET_CAPABILITY_OEM_PAID, NET_CAPABILITY_OEM_PRIVATE), false);
}
/**
@@ -1163,7 +1236,8 @@
.setOemPrivate(true)
.build();
sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
- sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PRIVATE));
+ sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PRIVATE),
+ false);
}
/**
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiSsidTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiSsidTest.java
new file mode 100644
index 0000000..acf8d28
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiSsidTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.wifi.WifiSsid;
+import android.os.Parcel;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+
+public class WifiSsidTest extends WifiJUnit3TestBase {
+
+ private static final String TEST_SSID_UTF_8 = "Test SSID";
+ private static final String TEST_SSID_UTF_8_QUOTED = "\"" + TEST_SSID_UTF_8 + "\"";
+ private static final byte[] TEST_SSID_UTF_8_BYTES =
+ TEST_SSID_UTF_8.getBytes(StandardCharsets.UTF_8);
+ private static final String TEST_SSID_UTF_8_HEX = "546573742053534944";
+
+ private static final byte[] TEST_SSID_NON_UTF_8_BYTES =
+ "服務集識別碼".getBytes(Charset.forName("GBK"));
+ private static final String TEST_SSID_NON_UTF_8_HEX = "B7FE84D5BCAFD7528465B461";
+
+ /**
+ * Verify the behavior of fromByteArray()
+ */
+ public void testFromByteArray() {
+ WifiSsid wifiSsidUtf8 = WifiSsid.fromBytes(TEST_SSID_UTF_8_BYTES);
+ assertThat(wifiSsidUtf8).isNotNull();
+ assertThat(wifiSsidUtf8.getBytes()).isEqualTo(TEST_SSID_UTF_8_BYTES);
+ assertThat(wifiSsidUtf8.getUtf8Text()).isEqualTo(TEST_SSID_UTF_8);
+ assertThat(wifiSsidUtf8.toString()).isEqualTo(TEST_SSID_UTF_8_QUOTED);
+
+ WifiSsid wifiSsidNonUtf8 = WifiSsid.fromBytes(TEST_SSID_NON_UTF_8_BYTES);
+ assertThat(wifiSsidNonUtf8).isNotNull();
+ assertThat(wifiSsidNonUtf8.getBytes()).isEqualTo(TEST_SSID_NON_UTF_8_BYTES);
+ assertThat(wifiSsidNonUtf8.getUtf8Text()).isNull();
+ assertThat(wifiSsidNonUtf8.toString()).isEqualTo(TEST_SSID_NON_UTF_8_HEX);
+
+ WifiSsid wifiSsidEmpty = WifiSsid.fromBytes(new byte[0]);
+ assertThat(wifiSsidEmpty).isNotNull();
+ assertThat(wifiSsidEmpty.getBytes()).isEmpty();
+ assertThat(wifiSsidEmpty.getUtf8Text().toString()).isEmpty();
+ assertThat(wifiSsidEmpty.toString()).isEmpty();
+
+ WifiSsid wifiSsidNull = WifiSsid.fromBytes(null);
+ assertThat(wifiSsidNull).isNotNull();
+ assertThat(wifiSsidNull.getBytes()).isEmpty();
+ assertThat(wifiSsidNull.getUtf8Text().toString()).isEmpty();
+ assertThat(wifiSsidNull.toString()).isEmpty();
+
+ try {
+ WifiSsid.fromBytes(new byte[33]);
+ fail("Expected IllegalArgumentException for byte array length greater than 32.");
+ } catch (IllegalArgumentException e) {
+ // Success
+ }
+ }
+
+ /**
+ * Verify the behavior of fromUtf8Text()
+ */
+ public void testfromUtf8Text() {
+ WifiSsid wifiSsidUtf8 = WifiSsid.fromUtf8Text(TEST_SSID_UTF_8);
+ assertThat(wifiSsidUtf8).isNotNull();
+ assertThat(wifiSsidUtf8.getBytes()).isEqualTo(TEST_SSID_UTF_8_BYTES);
+ assertThat(wifiSsidUtf8.getUtf8Text()).isEqualTo(TEST_SSID_UTF_8);
+ assertThat(wifiSsidUtf8.toString()).isEqualTo(TEST_SSID_UTF_8_QUOTED);
+
+ WifiSsid wifiSsidEmpty = WifiSsid.fromUtf8Text("");
+ assertThat(wifiSsidEmpty).isNotNull();
+ assertThat(wifiSsidEmpty.getBytes()).isEmpty();
+ assertThat(wifiSsidEmpty.getUtf8Text().toString()).isEmpty();
+ assertThat(wifiSsidEmpty.toString()).isEmpty();
+
+ WifiSsid wifiSsidNull = WifiSsid.fromUtf8Text(null);
+ assertThat(wifiSsidNull).isNotNull();
+ assertThat(wifiSsidNull.getBytes()).isEmpty();
+ assertThat(wifiSsidNull.getUtf8Text().toString()).isEmpty();
+ assertThat(wifiSsidNull.toString()).isEmpty();
+
+ try {
+ WifiSsid.fromUtf8Text("This is an SSID that is much longer than 32 bytes");
+ fail("Expected IllegalArgumentException for byte array length greater than 32.");
+ } catch (IllegalArgumentException e) {
+ // Success
+ }
+ }
+
+ /**
+ * Verify the behavior of fromString()
+ */
+ public void testFromString() {
+ WifiSsid wifiSsidUtf8 = WifiSsid.fromString(TEST_SSID_UTF_8_QUOTED);
+ assertThat(wifiSsidUtf8).isNotNull();
+ assertThat(wifiSsidUtf8.getBytes()).isEqualTo(TEST_SSID_UTF_8_BYTES);
+ assertThat(wifiSsidUtf8.getUtf8Text()).isEqualTo(TEST_SSID_UTF_8);
+ assertThat(wifiSsidUtf8.toString()).isEqualTo(TEST_SSID_UTF_8_QUOTED);
+
+ WifiSsid wifiSsidUtf8Hex = WifiSsid.fromString(TEST_SSID_UTF_8_HEX);
+ assertThat(wifiSsidUtf8Hex).isNotNull();
+ assertThat(wifiSsidUtf8Hex.getBytes()).isEqualTo(TEST_SSID_UTF_8_BYTES);
+ assertThat(wifiSsidUtf8Hex.getUtf8Text()).isEqualTo(TEST_SSID_UTF_8);
+ assertThat(wifiSsidUtf8Hex.toString()).isEqualTo(TEST_SSID_UTF_8_QUOTED);
+
+ WifiSsid wifiSsidNonUtf8 = WifiSsid.fromString(TEST_SSID_NON_UTF_8_HEX);
+ assertThat(wifiSsidNonUtf8).isNotNull();
+ assertThat(wifiSsidNonUtf8.getBytes()).isEqualTo(TEST_SSID_NON_UTF_8_BYTES);
+ assertThat(wifiSsidNonUtf8.getUtf8Text()).isNull();
+ assertThat(wifiSsidNonUtf8.toString()).isEqualTo(TEST_SSID_NON_UTF_8_HEX);
+
+ WifiSsid wifiSsidEmpty = WifiSsid.fromUtf8Text("");
+ assertThat(wifiSsidEmpty).isNotNull();
+ assertThat(wifiSsidEmpty.getBytes()).isEmpty();
+ assertThat(wifiSsidEmpty.getUtf8Text().toString()).isEmpty();
+ assertThat(wifiSsidEmpty.toString()).isEmpty();
+
+ WifiSsid wifiSsidNull = WifiSsid.fromUtf8Text(null);
+ assertThat(wifiSsidNull).isNotNull();
+ assertThat(wifiSsidNull.getBytes()).isEmpty();
+ assertThat(wifiSsidNull.getUtf8Text().toString()).isEmpty();
+ assertThat(wifiSsidNull.toString()).isEmpty();
+
+ try {
+ WifiSsid.fromString("\"This is an SSID that is much longer than 32 bytes\"");
+ fail("Expected IllegalArgumentException for byte array length greater than 32.");
+ } catch (IllegalArgumentException e) {
+ // Success
+ }
+
+ try {
+ WifiSsid.fromString(
+ "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789AB");
+ fail("Expected IllegalArgumentException for byte array length greater than 32.");
+ } catch (IllegalArgumentException e) {
+ // Success
+ }
+
+ try {
+ WifiSsid.fromString("0123456");
+ fail("Expected IllegalArgumentException for odd-length hexadecimal string");
+ } catch (IllegalArgumentException e) {
+ // Success
+ }
+ }
+
+ /**
+ * Verify that SSID created from bytes, UTF-8 String, and toString()-formatted String with the
+ * same content are equal.
+ *
+ * @throws Exception
+ */
+ public void testEquals() throws Exception {
+ WifiSsid fromBytesUtf8 = WifiSsid.fromBytes(TEST_SSID_UTF_8_BYTES);
+ WifiSsid fromUtf8TextUtf8 = WifiSsid.fromUtf8Text(TEST_SSID_UTF_8);
+ WifiSsid fromStringUtf8 = WifiSsid.fromString(TEST_SSID_UTF_8_QUOTED);
+ assertThat(fromBytesUtf8).isNotNull();
+ assertThat(fromUtf8TextUtf8).isNotNull();
+ assertThat(fromStringUtf8).isNotNull();
+ assertThat(fromBytesUtf8).isEqualTo(fromUtf8TextUtf8);
+ assertThat(fromBytesUtf8).isEqualTo(fromStringUtf8);
+ assertThat(fromUtf8TextUtf8).isEqualTo(fromStringUtf8);
+
+ WifiSsid fromBytesNonUtf8 = WifiSsid.fromBytes(TEST_SSID_NON_UTF_8_BYTES);
+ WifiSsid fromStringNonUtf8 = WifiSsid.fromString(TEST_SSID_NON_UTF_8_HEX);
+ assertThat(fromBytesNonUtf8).isNotNull();
+ assertThat(fromStringNonUtf8).isNotNull();
+ assertThat(fromBytesNonUtf8).isEqualTo(fromStringNonUtf8);
+
+ assertThat(fromBytesUtf8).isNotEqualTo(fromBytesNonUtf8);
+ }
+
+ /**
+ * Verify the behavior of the Parcelable interface implementation.
+ */
+ public void testParcelable() throws Exception {
+ List<WifiSsid> testWifiSsids = Arrays.asList(
+ WifiSsid.fromBytes(TEST_SSID_UTF_8_BYTES),
+ WifiSsid.fromBytes(TEST_SSID_NON_UTF_8_BYTES),
+ WifiSsid.fromUtf8Text(TEST_SSID_UTF_8),
+ WifiSsid.fromString(TEST_SSID_UTF_8_QUOTED),
+ WifiSsid.fromString(TEST_SSID_UTF_8_HEX),
+ WifiSsid.fromString(TEST_SSID_NON_UTF_8_HEX));
+
+ for (WifiSsid wifiSsid : testWifiSsids) {
+ Parcel parcel = Parcel.obtain();
+ wifiSsid.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ assertThat(WifiSsid.CREATOR.createFromParcel(parcel)).isEqualTo(wifiSsid);
+ }
+ }
+}
diff --git a/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java b/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
index a8866f2..b488fec 100644
--- a/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
@@ -24,6 +24,7 @@
import android.net.wifi.cts.WifiBuildCompat;
import android.net.wifi.rtt.RangingRequest;
import android.net.wifi.rtt.RangingResult;
+import android.net.wifi.rtt.ResponderConfig;
import android.net.wifi.rtt.ResponderLocation;
import android.platform.test.annotations.AppModeFull;
@@ -47,7 +48,7 @@
private static final int NUM_OF_RTT_ITERATIONS = 10;
// Maximum failure rate of RTT measurements (percentage)
- private static final int MAX_FAILURE_RATE_PERCENT = 10;
+ private static final int MAX_FAILURE_RATE_PERCENT = 20;
// Maximum variation from the average measurement (measures consistency)
private static final int MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM = 2000;
@@ -55,6 +56,9 @@
// Maximum failure rate of one-sided RTT measurements (percentage)
private static final int MAX_NON11MC_FAILURE_RATE_PERCENT = 40;
+ // Maximum non-8011mc variation from the average measurement (measures consistency)
+ private static final int MAX_NON11MC_VARIATION_FROM_AVERAGE_DISTANCE_MM = 4000;
+
// Minimum valid RSSI value
private static final int MIN_VALID_RSSI = -100;
@@ -65,14 +69,14 @@
private static final int intervalMs = 200;
/**
- * Test Wi-Fi RTT ranging operation:
+ * Test Wi-Fi RTT ranging operation using ScanResults in request:
* - Scan for visible APs for the test AP (which is validated to support IEEE 802.11mc)
* - Perform N (constant) RTT operations
* - Validate:
* - Failure ratio < threshold (constant)
* - Result margin < threshold (constant)
*/
- public void testRangingToTest11mcAp() throws InterruptedException {
+ public void testRangingToTest11mcApUsingScanResult() throws InterruptedException {
if (!shouldTestWifiRtt(getContext())) {
return;
}
@@ -82,10 +86,10 @@
assertNotNull(
"Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
+ "your test setup includes them!", testAp);
-
// Perform RTT operations
RangingRequest.Builder builder = new RangingRequest.Builder();
builder.addAccessPoint(testAp);
+
if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
assertTrue(RangingRequest.getDefaultRttBurstSize()
@@ -93,11 +97,124 @@
assertTrue(RangingRequest.getDefaultRttBurstSize()
<= RangingRequest.getMaxRttBurstSize());
}
+
RangingRequest request = builder.build();
if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
assertEquals(1, request.getRttResponders().size());
}
+ range11mcApRequest(request, testAp);
+ }
+ /**
+ * Test Wi-Fi RTT ranging using ResponderConfig in the single responder RangingRequest API.
+ * - Scan for visible APs for the test AP (which is validated to support IEEE 802.11mc)
+ * - Perform N (constant) RTT operations
+ * - Validate:
+ * - Failure ratio < threshold (constant)
+ * - Result margin < threshold (constant)
+ */
+ public void testRangingToTest11mcApUsingResponderConfig() throws InterruptedException {
+ if (!shouldTestWifiRtt(getContext())) {
+ return;
+ }
+
+ // Scan for IEEE 802.11mc supporting APs
+ ScanResult testAp = scanForTest11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+ assertNotNull(
+ "Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
+ + "your test setup includes them!", testAp);
+ int preamble = ResponderConfig.fromScanResult(testAp).getPreamble();
+
+ // Create a ResponderConfig from the builder API.
+ ResponderConfig.Builder responderBuilder = new ResponderConfig.Builder();
+ ResponderConfig responder = responderBuilder
+ .setMacAddress(MacAddress.fromString(testAp.BSSID))
+ .set80211mcSupported(testAp.is80211mcResponder())
+ .setChannelWidth(testAp.channelWidth)
+ .setFrequencyMhz(testAp.frequency)
+ .setCenterFreq0Mhz(testAp.centerFreq0)
+ .setCenterFreq1Mhz(testAp.centerFreq1)
+ .setPreamble(preamble)
+ .build();
+
+ // Validate ResponderConfig.Builder set method arguments match getter methods.
+ assertTrue(responder.getMacAddress().toString().equalsIgnoreCase(testAp.BSSID)
+ && responder.is80211mcSupported() == testAp.is80211mcResponder()
+ && responder.getChannelWidth() == testAp.channelWidth
+ && responder.getFrequencyMhz() == testAp.frequency
+ && responder.getCenterFreq0Mhz() == testAp.centerFreq0
+ && responder.getCenterFreq1Mhz() == testAp.centerFreq1
+ && responder.getPreamble() == preamble);
+
+ // Perform RTT operations
+ RangingRequest.Builder builder = new RangingRequest.Builder();
+ builder.addResponder(responder);
+
+ if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
+ builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
+ assertTrue(RangingRequest.getDefaultRttBurstSize()
+ >= RangingRequest.getMinRttBurstSize());
+ assertTrue(RangingRequest.getDefaultRttBurstSize()
+ <= RangingRequest.getMaxRttBurstSize());
+ }
+
+ RangingRequest request = builder.build();
+
+ if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
+ assertEquals(1, request.getRttResponders().size());
+ }
+ range11mcApRequest(request, testAp);
+ }
+
+ /**
+ * Test Wi-Fi RTT ranging using ResponderConfig in the multi-Responder RangingRequest API.
+ * - Scan for visible APs for the test AP (which is validated to support IEEE 802.11mc)
+ * - Perform N (constant) RTT operations
+ * - Validate:
+ * - Failure ratio < threshold (constant)
+ * - Result margin < threshold (constant)
+ */
+ public void testRangingToTest11mcApUsingListResponderConfig() throws InterruptedException {
+ if (!shouldTestWifiRtt(getContext())) {
+ return;
+ }
+
+ // Scan for IEEE 802.11mc supporting APs
+ ScanResult testAp = scanForTest11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+ assertNotNull(
+ "Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
+ + "your test setup includes them!", testAp);
+ ResponderConfig responder = ResponderConfig.fromScanResult(testAp);
+ // Perform RTT operations
+ RangingRequest.Builder builder = new RangingRequest.Builder();
+ List<ResponderConfig> responders = new ArrayList<>();
+ responders.add(responder);
+ builder.addResponders(responders);
+
+ if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
+ builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
+ assertTrue(RangingRequest.getDefaultRttBurstSize()
+ >= RangingRequest.getMinRttBurstSize());
+ assertTrue(RangingRequest.getDefaultRttBurstSize()
+ <= RangingRequest.getMaxRttBurstSize());
+ }
+
+ RangingRequest request = builder.build();
+
+ if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
+ assertEquals(1, request.getRttResponders().size());
+ }
+ range11mcApRequest(request, testAp);
+ }
+
+ /**
+ * Utility method for validating a ranging request.
+ *
+ * @param request the ranging request that is being tested
+ * @param testAp the original test scan result to provide feedback on failure conditions
+ */
+ private void range11mcApRequest(RangingRequest request, ScanResult testAp)
+ throws InterruptedException {
List<RangingResult> allResults = new ArrayList<>();
int numFailures = 0;
int distanceSum = 0;
@@ -217,6 +334,8 @@
}
}
+
+
/**
* Validate that when a request contains more range operations than allowed (by API) that we
* get an exception.
@@ -451,7 +570,7 @@
}
/**
- * Test Wi-Fi One-sided RTT ranging operation:
+ * Test Wi-Fi One-sided RTT ranging operation using ScanResult in request:
* - Scan for visible APs for the test AP (which do not support IEEE 802.11mc) and are operating
* - in the 5GHz band.
* - Perform N (constant) RTT operations
@@ -460,7 +579,7 @@
* - Failure ratio < threshold (constant)
* - Result margin < threshold (constant)
*/
- public void testRangingToTestNon11mcAp() throws InterruptedException {
+ public void testRangingToTestNon11mcApUsingScanResult() throws InterruptedException {
if (!shouldTestWifiRtt(getContext())
|| !WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
return;
@@ -478,6 +597,52 @@
builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
RangingRequest request = builder.build();
+ // Perform the rquest
+ rangeNon11mcApRequest(request, testAp, MAX_NON11MC_VARIATION_FROM_AVERAGE_DISTANCE_MM);
+ }
+
+ /**
+ * Test Wi-Fi one-sided RTT ranging operation using ResponderConfig in request:
+ * - Scan for visible APs for the test AP (which do not support IEEE 802.11mc) and are operating
+ * - in the 5GHz band.
+ * - Perform N (constant) RTT operations
+ * - Remove outliers while insuring greater than 50% of the results still remain
+ * - Validate:
+ * - Failure ratio < threshold (constant)
+ * - Result margin < threshold (constant)
+ */
+ public void testRangingToTestNon11mcApUsingResponderConfig() throws InterruptedException {
+ if (!shouldTestWifiRtt(getContext())
+ || !WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
+ return;
+ }
+
+ // Scan for Non-IEEE 802.11mc supporting APs
+ ScanResult testAp = scanForTestNon11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+ assertNotNull(
+ "Cannot find any test APs which are Non-IEEE 802.11mc - please verify that"
+ + " your test setup includes them!", testAp);
+
+ ResponderConfig responder = ResponderConfig.fromScanResult(testAp);
+
+ // Perform RTT operations
+ RangingRequest.Builder builder = new RangingRequest.Builder();
+ builder.addResponder(responder);
+ builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
+ RangingRequest request = builder.build();
+
+ // Perform the rquest
+ rangeNon11mcApRequest(request, testAp, MAX_NON11MC_VARIATION_FROM_AVERAGE_DISTANCE_MM);
+ }
+
+ /**
+ * Utility method for validating a ranging request to a non-80211mc AP.
+ *
+ * @param request the ranging request that is being tested
+ * @param testAp the original test scan result to provide feedback on failure conditions
+ */
+ private void rangeNon11mcApRequest(RangingRequest request, ScanResult testAp,
+ int variationLimit) throws InterruptedException {
List<RangingResult> allResults = new ArrayList<>();
int numFailures = 0;
int distanceSum = 0;
@@ -571,55 +736,59 @@
ResultType.NEUTRAL, ResultUnit.NONE);
reportLog.submit();
- /** TODO(b/192909380): enable the performance verification after device fix.
- // Analyze results
- assertTrue("Wi-Fi RTT failure rate exceeds threshold: FAIL=" + numFailures
- + ", ITERATIONS="
- + NUM_OF_RTT_ITERATIONS + ", AP RSSI=" + testAp.level
- + ", AP SSID=" + testAp.SSID,
- numFailures <= NUM_OF_RTT_ITERATIONS * MAX_NON11MC_FAILURE_RATE_PERCENT / 100);
+ // This bug below has been addressed by making the test parameters for Non-80211mc devices
+ // less stringent. Please update the bug if this does not solve the problem.
+ // TODO(b/192909380): enable the performance verification after device fix.
- if (numFailures != NUM_OF_RTT_ITERATIONS) {
- // Calculate an initial average using all measurements to determine distance outliers
- double distanceAvg = (double) distanceSum / (NUM_OF_RTT_ITERATIONS - numFailures);
- // Now figure out the distance outliers and mark them in the distance inclusion map
- int validDistances = 0;
- for (int i = 0; i < (NUM_OF_RTT_ITERATIONS - numFailures); i++) {
- if (distanceMms[i] - MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM < distanceAvg) {
- // Distances that are in range for the distribution are included in the map
- distanceInclusionMap[i] = true;
- validDistances++;
- } else {
- // Distances that are out of range for the distribution are excluded in the map
- distanceInclusionMap[i] = false;
- }
- }
+ // Analyze results
+ assertTrue("Wi-Fi RTT failure rate exceeds threshold: FAIL=" + numFailures
+ + ", ITERATIONS="
+ + NUM_OF_RTT_ITERATIONS + ", AP RSSI=" + testAp.level
+ + ", AP SSID=" + testAp.SSID,
+ numFailures <= NUM_OF_RTT_ITERATIONS * MAX_NON11MC_FAILURE_RATE_PERCENT / 100);
- assertTrue("After fails+outlier removal greater that 50% distances must remain: " +
- NUM_OF_RTT_ITERATIONS / 2, validDistances > NUM_OF_RTT_ITERATIONS / 2);
+ if (numFailures != NUM_OF_RTT_ITERATIONS) {
+ // Calculate an initial average using all measurements to determine distance outliers
+ double distanceAvg = (double) distanceSum / (NUM_OF_RTT_ITERATIONS - numFailures);
+ // Now figure out the distance outliers and mark them in the distance inclusion map
+ int validDistances = 0;
+ for (int i = 0; i < (NUM_OF_RTT_ITERATIONS - numFailures); i++) {
+ if (distanceMms[i] - variationLimit < distanceAvg) {
+ // Distances that are in range for the distribution are included in the map
+ distanceInclusionMap[i] = true;
+ validDistances++;
+ } else {
+ // Distances that are out of range for the distribution are excluded in the map
+ distanceInclusionMap[i] = false;
+ }
+ }
- // Remove the distance outliers and find the new average, min and max.
- distanceSum = 0;
- distanceMax = Integer.MIN_VALUE;
- distanceMin = Integer.MAX_VALUE;
- for (int i = 0; i < (NUM_OF_RTT_ITERATIONS - numFailures); i++) {
- if (distanceInclusionMap[i]) {
- distanceSum += distanceMms[i];
- distanceMin = Math.min(distanceMin, distanceMms[i]);
- distanceMax = Math.max(distanceMax, distanceMms[i]);
- }
+ assertTrue("After fails+outlier removal greater that 50% distances must remain: "
+ + NUM_OF_RTT_ITERATIONS / 2, validDistances > NUM_OF_RTT_ITERATIONS / 2);
+
+ // Remove the distance outliers and find the new average, min and max.
+ distanceSum = 0;
+ distanceMax = Integer.MIN_VALUE;
+ distanceMin = Integer.MAX_VALUE;
+ for (int i = 0; i < (NUM_OF_RTT_ITERATIONS - numFailures); i++) {
+ if (distanceInclusionMap[i]) {
+ distanceSum += distanceMms[i];
+ distanceMin = Math.min(distanceMin, distanceMms[i]);
+ distanceMax = Math.max(distanceMax, distanceMms[i]);
}
- distanceAvg = (double) distanceSum / validDistances;
- assertTrue("Wi-Fi RTT: Variation (max direction) exceeds threshold, Variation ="
- + (distanceMax - distanceAvg),
- (distanceMax - distanceAvg) <= MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM);
- assertTrue("Wi-Fi RTT: Variation (min direction) exceeds threshold, Variation ="
- + (distanceAvg - distanceMin),
- (distanceAvg - distanceMin) <= MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM);
- for (int i = 0; i < numGoodResults; ++i) {
- assertNotSame("Number of attempted measurements is 0", 0, numAttempted[i]);
- assertNotSame("Number of successful measurements is 0", 0, numSuccessful[i]);
- }
- */
+ }
+ distanceAvg = (double) distanceSum / validDistances;
+ assertTrue("Wi-Fi RTT: Variation (max direction) exceeds threshold, Variation ="
+ + (distanceMax - distanceAvg),
+ (distanceMax - distanceAvg) <= variationLimit);
+ assertTrue("Wi-Fi RTT: Variation (min direction) exceeds threshold, Variation ="
+ + (distanceAvg - distanceMin),
+ (distanceAvg - distanceMin) <= variationLimit);
+ for (int i = 0; i < numGoodResults; ++i) {
+ assertNotSame("Number of attempted measurements is 0", 0, numAttempted[i]);
+ assertNotSame("Number of successful measurements is 0", 0, numSuccessful[i]);
+ }
+ }
+
}
}
diff --git a/tests/translation/AndroidManifest.xml b/tests/translation/AndroidManifest.xml
index b20fe08..1ed4560 100644
--- a/tests/translation/AndroidManifest.xml
+++ b/tests/translation/AndroidManifest.xml
@@ -18,6 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.translation.cts">
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application android:label="Translation TestCase">
<uses-library android:name="android.test.runner"/>
diff --git a/tests/translation/AndroidTest.xml b/tests/translation/AndroidTest.xml
index 6f213f5..e113244 100644
--- a/tests/translation/AndroidTest.xml
+++ b/tests/translation/AndroidTest.xml
@@ -41,4 +41,10 @@
<!-- dismiss all system dialogs before launch test -->
<option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS" />
</target_preparer>
+ <!-- Collect the files generated on error -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/sdcard/CtsTranslationTestCases" />
+ <option name="collect-on-run-ended-only" value="true" />
+ <option name="clean-up" value="false" />
+ </metrics_collector>
</configuration>
diff --git a/tests/translation/src/android/translation/cts/CtsTestIme.java b/tests/translation/src/android/translation/cts/CtsTestIme.java
index b07be05..8533989 100644
--- a/tests/translation/src/android/translation/cts/CtsTestIme.java
+++ b/tests/translation/src/android/translation/cts/CtsTestIme.java
@@ -194,7 +194,7 @@
filter.addAction(ACTION_ASSERT_UI_TRANSLATION_CALLBACK_ON_FINISH);
filter.addAction(ACTION_ASSERT_UI_TRANSLATION_CALLBACK_ON_RESUME);
filter.addAction(ACTION_ASSERT_UI_TRANSLATION_CALLBACK_ON_PAUSE);
- mContext.registerReceiver(this, filter);
+ mContext.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED);
}
void unRegister() {
diff --git a/tests/translation/src/android/translation/cts/CtsTranslationService.java b/tests/translation/src/android/translation/cts/CtsTranslationService.java
index 49f70cd..b7d50d5 100644
--- a/tests/translation/src/android/translation/cts/CtsTranslationService.java
+++ b/tests/translation/src/android/translation/cts/CtsTranslationService.java
@@ -65,6 +65,8 @@
private final CountDownLatch mSessionDestroyedLatch = new CountDownLatch(1);
+ private TranslationContext mTranslationContext;
+
/**
* Timeout for Translation cts.
*/
@@ -109,6 +111,7 @@
public void onCreateTranslationSession(@NonNull TranslationContext translationContext,
int sessionId, @NonNull Consumer<Boolean> callback) {
Log.v(TAG, "onCreateTranslationSession");
+ mTranslationContext = translationContext;
callback.accept(true);
}
@@ -116,6 +119,7 @@
public void onFinishTranslationSession(int sessionId) {
Log.v(TAG, "onFinishTranslationSession");
mSessionDestroyedLatch.countDown();
+ mTranslationContext = null;
}
@Override
@@ -149,6 +153,10 @@
return sServiceWatcher;
}
+ TranslationContext getTranslationContext() {
+ return mTranslationContext;
+ }
+
/**
* Wait the Translation session destroyed.
*/
diff --git a/tests/translation/src/android/translation/cts/Helper.java b/tests/translation/src/android/translation/cts/Helper.java
index 12e4e66..0b4c7f8 100644
--- a/tests/translation/src/android/translation/cts/Helper.java
+++ b/tests/translation/src/android/translation/cts/Helper.java
@@ -18,18 +18,29 @@
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
import android.content.ContentCaptureOptions;
import android.content.Context;
+import android.graphics.Bitmap;
import android.os.UserHandle;
import android.util.Log;
-import android.view.contentcapture.ContentCaptureContext;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
+import com.android.compatibility.common.util.BitmapUtils;
+import com.android.compatibility.common.util.TestNameUtils;
+
+import java.io.File;
+import java.io.IOException;
+
/**
* Helper for common funcionalities.
*/
@@ -58,6 +69,7 @@
public static final String EXTRA_VERIFY_RESULT = "verify_result";
public static final String CUSTOM_TRANSLATION_ID_MY_TAG = "myTag";
+ public static final String LOCAL_TEST_FILES_DIR = "/sdcard/CtsTranslationTestCases";
private static final String LOG_TAG = "log.tag.UiTranslation";
/**
@@ -166,4 +178,69 @@
Log.d(TAG, "disableDebugLog(), set level " + level);
System.setProperty(LOG_TAG, level);
}
-}
\ No newline at end of file
+
+ // TODO: Move to a library that can be shared for smart os components.
+ /**
+ * Takes a screenshot and save it in the file system for analysis.
+ */
+ public static void takeScreenshotAndSave(Context context, String testName,
+ String targetFolder) {
+ File file = null;
+ try {
+ file = createTestFile(testName,"sreenshot.png", targetFolder);
+ if (file != null) {
+ Log.i(TAG, "Taking screenshot on " + file);
+ final Bitmap screenshot = takeScreenshot();
+ saveBitmapToFile(screenshot, file);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error taking screenshot and saving on " + file, e);
+ }
+ }
+
+ public static File saveBitmapToFile(Bitmap bitmap, File file) {
+ Log.i(TAG, "Saving bitmap at " + file);
+ BitmapUtils.saveBitmap(bitmap, file.getParent(), file.getName());
+ return file;
+ }
+
+ private static Bitmap takeScreenshot() {
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ UiAutomation automan = instrumentation.getUiAutomation();
+ final Bitmap bitmap = automan.takeScreenshot();
+ return bitmap;
+ }
+
+ public static File createTestFile(String testName, String name, String targetFolder)
+ throws IOException {
+ final File dir = getLocalDirectory(targetFolder);
+ if (dir == null) return null;
+ final String prefix = testName.replaceAll("\\.|\\(|\\/", "_").replaceAll("\\)", "");
+ final String filename = prefix + "-" + name;
+
+ return createFile(dir, filename);
+ }
+
+ private static File getLocalDirectory(String targetFolder) {
+ final File dir = new File(targetFolder);
+ dir.mkdirs();
+ if (!dir.exists()) {
+ Log.e(TAG, "Could not create directory " + dir);
+ return null;
+ }
+ return dir;
+ }
+
+ private static File createFile(File dir, String filename) throws IOException {
+ final File file = new File(dir, filename);
+ if (file.exists()) {
+ Log.v(TAG, "Deleting file " + file);
+ file.delete();
+ }
+ if (!file.createNewFile()) {
+ Log.e(TAG, "Could not create file " + file);
+ return null;
+ }
+ return file;
+ }
+}
diff --git a/tests/translation/src/android/translation/cts/TranslationManagerTest.java b/tests/translation/src/android/translation/cts/TranslationManagerTest.java
index 128f5a8..fa21041 100644
--- a/tests/translation/src/android/translation/cts/TranslationManagerTest.java
+++ b/tests/translation/src/android/translation/cts/TranslationManagerTest.java
@@ -257,6 +257,12 @@
sTranslationReplier.getNextTranslationRequest();
+ CtsTranslationService translationService =
+ mServiceWatcher.getService();
+ TranslationContext sessionContext = translationService.getTranslationContext();
+ assertThat(sessionContext).isNotNull();
+ assertThat(sessionContext.getActivityId()).isNull();
+
translator.destroy();
assertThat(translator.isDestroyed()).isTrue();
try {
diff --git a/tests/translation/src/android/translation/cts/TranslationTestWatcher.java b/tests/translation/src/android/translation/cts/TranslationTestWatcher.java
new file mode 100644
index 0000000..ba80df2
--- /dev/null
+++ b/tests/translation/src/android/translation/cts/TranslationTestWatcher.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.translation.cts;
+
+import android.util.Log;
+
+import com.android.compatibility.common.util.TestNameUtils;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+/**
+ * Custom {@link TestWatcher} that used for UiTranslationManagerTest.
+ */
+public final class TranslationTestWatcher extends TestWatcher {
+
+ private static final String TAG = "TranslationTestWatcher";
+
+ @Override
+ protected void starting(Description description) {
+ final String testName = description.getDisplayName();
+ Log.i(TAG, "Starting " + testName);
+ TestNameUtils.setCurrentTestName(testName);
+ }
+
+ @Override
+ protected void finished(Description description) {
+ final String testName = description.getDisplayName();
+ Log.i(TAG, "Finished " + testName);
+ TestNameUtils.setCurrentTestName(null);
+ }
+}
diff --git a/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java b/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
index 34ceb72..0f82b87 100644
--- a/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
+++ b/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
@@ -57,6 +57,7 @@
import android.view.autofill.AutofillId;
import android.view.contentcapture.ContentCaptureContext;
import android.view.inputmethod.InputMethodManager;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationRequest;
import android.view.translation.TranslationResponse;
import android.view.translation.TranslationResponseValue;
@@ -79,6 +80,7 @@
import com.android.compatibility.common.util.BlockingBroadcastReceiver;
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.RequiredServiceRule;
+import com.android.compatibility.common.util.TestNameUtils;
import org.junit.After;
import org.junit.AfterClass;
@@ -113,7 +115,7 @@
private static final String TAG = "UiTranslationManagerTest";
- private static final long UI_WAIT_TIMEOUT = 2000;
+ private static final long UI_WAIT_TIMEOUT = 2500;
// TODO: Use fw definition when it becomes public or testapi
private static final String ID_CONTENT_DESCRIPTION = "android:content_description";
@@ -141,6 +143,9 @@
public final RequiredServiceRule mTranslationServiceRule =
new RequiredServiceRule(TRANSLATION_MANAGER_SERVICE);
+ @Rule
+ public final TranslationTestWatcher mTranslationTestWatcher = new TranslationTestWatcher();
+
@BeforeClass
public static void oneTimeSetup() {
sContext = ApplicationProvider.getApplicationContext();
@@ -178,68 +183,81 @@
@Test
public void testUiTranslation() throws Throwable {
- final Pair<List<AutofillId>, ContentCaptureContext> result =
- enableServicesAndStartActivityForTranslation();
+ try {
+ final Pair<List<AutofillId>, ContentCaptureContext> result =
+ enableServicesAndStartActivityForTranslation();
- final CharSequence originalText = mTextView.getText();
- final List<AutofillId> views = result.first;
- final ContentCaptureContext contentCaptureContext = result.second;
+ final CharSequence originalText = mTextView.getText();
+ final List<AutofillId> views = result.first;
+ final ContentCaptureContext contentCaptureContext = result.second;
- final String translatedText = "success";
- final UiObject2 helloText = Helper.findObjectByResId(Helper.ACTIVITY_PACKAGE,
- SimpleActivity.HELLO_TEXT_ID);
- assertThat(helloText).isNotNull();
- // Set response
- final TranslationResponse response = createViewsTranslationResponse(views, translatedText);
- sTranslationReplier.addResponse(response);
+ final String translatedText = "success";
+ final UiObject2 helloText = Helper.findObjectByResId(Helper.ACTIVITY_PACKAGE,
+ SimpleActivity.HELLO_TEXT_ID);
+ assertThat(helloText).isNotNull();
+ // Set response
+ final TranslationResponse response =
+ createViewsTranslationResponse(views, translatedText);
+ sTranslationReplier.addResponse(response);
- startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
- // Check request
- final TranslationRequest request = sTranslationReplier.getNextTranslationRequest();
- final List<ViewTranslationRequest> requests = request.getViewTranslationRequests();
- final ViewTranslationRequest viewRequest = requests.get(0);
- assertThat(viewRequest.getAutofillId()).isEqualTo(views.get(0));
- assertThat(viewRequest.getKeys().size()).isEqualTo(1);
- assertThat(viewRequest.getKeys()).containsExactly(ViewTranslationRequest.ID_TEXT);
- assertThat(viewRequest.getValue(ViewTranslationRequest.ID_TEXT).getText())
- .isEqualTo(originalText.toString());
+ // Check request
+ final TranslationRequest request = sTranslationReplier.getNextTranslationRequest();
+ final List<ViewTranslationRequest> requests = request.getViewTranslationRequests();
+ final ViewTranslationRequest viewRequest = requests.get(0);
+ assertThat(viewRequest.getAutofillId()).isEqualTo(views.get(0));
+ assertThat(viewRequest.getKeys().size()).isEqualTo(1);
+ assertThat(viewRequest.getKeys()).containsExactly(ViewTranslationRequest.ID_TEXT);
+ assertThat(viewRequest.getValue(ViewTranslationRequest.ID_TEXT).getText())
+ .isEqualTo(originalText.toString());
+ CtsTranslationService translationService =
+ mTranslationServiceServiceWatcher.getService();
+ TranslationContext translationContext = translationService.getTranslationContext();
+ assertThat(translationContext).isNotNull();
+ assertThat(translationContext.getActivityId()).isNotNull();
+ assertThat(translationContext.getActivityId())
+ .isEqualTo(contentCaptureContext.getActivityId());
- assertThat(helloText.getText()).isEqualTo(translatedText);
- assertThat(mTextView.getViewTranslationResponse())
- .isEqualTo(response.getViewTranslationResponses().get(0));
+ assertThat(helloText.getText()).isEqualTo(translatedText);
+ assertThat(mTextView.getViewTranslationResponse())
+ .isEqualTo(response.getViewTranslationResponses().get(0));
- pauseUiTranslation(contentCaptureContext);
+ pauseUiTranslation(contentCaptureContext);
- assertThat(helloText.getText()).isEqualTo(originalText.toString());
+ assertThat(helloText.getText()).isEqualTo(originalText.toString());
- resumeUiTranslation(contentCaptureContext);
+ resumeUiTranslation(contentCaptureContext);
- assertThat(helloText.getText()).isEqualTo(translatedText);
+ assertThat(helloText.getText()).isEqualTo(translatedText);
- finishUiTranslation(contentCaptureContext);
+ finishUiTranslation(contentCaptureContext);
- assertThat(helloText.getText()).isEqualTo(originalText.toString());
+ assertThat(helloText.getText()).isEqualTo(originalText.toString());
- // Check the Translation session is destroyed after calling finishTranslation()
- CtsTranslationService translationService =
- mTranslationServiceServiceWatcher.getService();
- translationService.awaitSessionDestroyed();
+ // Check the Translation session is destroyed after calling finishTranslation()
+ translationService.awaitSessionDestroyed();
- // Test re-translating.
- sTranslationReplier.addResponse(createViewsTranslationResponse(views, translatedText));
+ // Test re-translating.
+ sTranslationReplier.addResponse(createViewsTranslationResponse(views, translatedText));
- startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
- assertThat(helloText.getText()).isEqualTo(translatedText);
+ assertThat(helloText.getText()).isEqualTo(translatedText);
- // Also make sure pausing still works.
- pauseUiTranslation(contentCaptureContext);
+ // Also make sure pausing still works.
+ pauseUiTranslation(contentCaptureContext);
- assertThat(helloText.getText()).isEqualTo(originalText.toString());
+ assertThat(helloText.getText()).isEqualTo(originalText.toString());
+ } catch (Throwable t) {
+ Helper.takeScreenshotAndSave(sContext, TestNameUtils.getCurrentTestName(),
+ Helper.LOCAL_TEST_FILES_DIR);
+ throw t;
+ }
}
@Test
+ @FlakyTest(bugId = 192418800)
public void testPauseUiTranslationThenStartUiTranslation() throws Throwable {
final Pair<List<AutofillId>, ContentCaptureContext> result =
enableServicesAndStartActivityForTranslation();
@@ -321,82 +339,94 @@
@Test
@FlakyTest(bugId = 192418800)
public void testUiTranslation_ViewTranslationCallback_paddingText() throws Throwable {
- final Pair<List<AutofillId>, ContentCaptureContext> result =
- enableServicesAndStartActivityForTranslation();
- final List<AutofillId> views = result.first;
- final ContentCaptureContext contentCaptureContext = result.second;
+ try {
+ final Pair<List<AutofillId>, ContentCaptureContext> result =
+ enableServicesAndStartActivityForTranslation();
+ final List<AutofillId> views = result.first;
+ final ContentCaptureContext contentCaptureContext = result.second;
- // Set response
- final CharSequence originalText = mTextView.getText();
- final CharSequence translatedText = "Translated World";
- sTranslationReplier.addResponse(
- createViewsTranslationResponse(views, translatedText.toString()));
+ // Set response
+ final CharSequence originalText = mTextView.getText();
+ final CharSequence translatedText = "Translated World";
+ sTranslationReplier.addResponse(
+ createViewsTranslationResponse(views, translatedText.toString()));
- // Use TextView default ViewTranslationCallback implementation
- startUiTranslation(/* shouldPadContent */ true, views, contentCaptureContext);
+ // Use TextView default ViewTranslationCallback implementation
+ startUiTranslation(/* shouldPadContent */ true, views, contentCaptureContext);
- CharSequence currentText = mTextView.getText();
- assertThat(currentText.length()).isNotEqualTo(originalText.length());
- assertThat(currentText.length()).isEqualTo(translatedText.length());
+ CharSequence currentText = mTextView.getText();
+ assertThat(currentText.length()).isNotEqualTo(originalText.length());
+ assertThat(currentText.length()).isEqualTo(translatedText.length());
- finishUiTranslation(contentCaptureContext);
+ finishUiTranslation(contentCaptureContext);
- // Set Customized ViewTranslationCallback
- ViewTranslationCallback mockCallback = Mockito.mock(ViewTranslationCallback.class);
- mTextView.setViewTranslationCallback(mockCallback);
+ // Set Customized ViewTranslationCallback
+ ViewTranslationCallback mockCallback = Mockito.mock(ViewTranslationCallback.class);
+ mTextView.setViewTranslationCallback(mockCallback);
- startUiTranslation(/* shouldPadContent */ true, views, contentCaptureContext);
+ startUiTranslation(/* shouldPadContent */ true, views, contentCaptureContext);
- assertThat(mTextView.getText().length()).isEqualTo(originalText.length());
+ assertThat(mTextView.getText().length()).isEqualTo(originalText.length());
+ } catch (Throwable t) {
+ Helper.takeScreenshotAndSave(sContext, TestNameUtils.getCurrentTestName(),
+ Helper.LOCAL_TEST_FILES_DIR);
+ throw t;
+ }
}
@Test
public void testUiTranslation_hasContentDescription() throws Throwable {
- final Pair<List<AutofillId>, ContentCaptureContext> result =
- enableServicesAndStartActivityForTranslation();
- final List<AutofillId> views = result.first;
- final ContentCaptureContext contentCaptureContext = result.second;
+ try {
+ final Pair<List<AutofillId>, ContentCaptureContext> result =
+ enableServicesAndStartActivityForTranslation();
+ final List<AutofillId> views = result.first;
+ final ContentCaptureContext contentCaptureContext = result.second;
- // Set response
- final CharSequence translatedText = "Translated World";
- final CharSequence originalDescription = "Hello Description";
- mActivityScenario.onActivity(activity -> {
- mTextView.setContentDescription(originalDescription);
- });
- sTranslationReplier.addResponse(
- createViewsTranslationResponse(views, translatedText.toString()));
+ // Set response
+ final CharSequence translatedText = "Translated World";
+ final CharSequence originalDescription = "Hello Description";
+ mActivityScenario.onActivity(activity -> {
+ mTextView.setContentDescription(originalDescription);
+ });
+ sTranslationReplier.addResponse(
+ createViewsTranslationResponse(views, translatedText.toString()));
- // Use TextView default ViewTranslationCallback implementation
- startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+ // Use TextView default ViewTranslationCallback implementation
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
- assertThat(mTextView.getContentDescription().toString())
- .isEqualTo(translatedText.toString());
+ assertThat(mTextView.getContentDescription().toString())
+ .isEqualTo(translatedText.toString());
- // Check request to make sure the content description key doesn't be changed
- final TranslationRequest request = sTranslationReplier.getNextTranslationRequest();
- final List<ViewTranslationRequest> requests = request.getViewTranslationRequests();
- final ViewTranslationRequest viewRequest = requests.get(0);
- assertThat(viewRequest.getAutofillId()).isEqualTo(views.get(0));
- assertThat(viewRequest.getKeys().size()).isEqualTo(2);
- assertThat(viewRequest.getKeys()).containsExactly(ID_CONTENT_DESCRIPTION,
- ViewTranslationRequest.ID_TEXT);
- assertThat(viewRequest.getValue(ID_CONTENT_DESCRIPTION).getText())
- .isEqualTo(originalDescription);
+ // Check request to make sure the content description key doesn't be changed
+ final TranslationRequest request = sTranslationReplier.getNextTranslationRequest();
+ final List<ViewTranslationRequest> requests = request.getViewTranslationRequests();
+ final ViewTranslationRequest viewRequest = requests.get(0);
+ assertThat(viewRequest.getAutofillId()).isEqualTo(views.get(0));
+ assertThat(viewRequest.getKeys().size()).isEqualTo(2);
+ assertThat(viewRequest.getKeys()).containsExactly(ID_CONTENT_DESCRIPTION,
+ ViewTranslationRequest.ID_TEXT);
+ assertThat(viewRequest.getValue(ID_CONTENT_DESCRIPTION).getText())
+ .isEqualTo(originalDescription);
- pauseUiTranslation(contentCaptureContext);
+ pauseUiTranslation(contentCaptureContext);
- assertThat(mTextView.getContentDescription().toString())
- .isEqualTo(originalDescription.toString());
+ assertThat(mTextView.getContentDescription().toString())
+ .isEqualTo(originalDescription.toString());
- resumeUiTranslation(contentCaptureContext);
+ resumeUiTranslation(contentCaptureContext);
- assertThat(mTextView.getContentDescription().toString())
- .isEqualTo(translatedText.toString());
+ assertThat(mTextView.getContentDescription().toString())
+ .isEqualTo(translatedText.toString());
- finishUiTranslation(contentCaptureContext);
+ finishUiTranslation(contentCaptureContext);
- assertThat(mTextView.getContentDescription().toString())
- .isEqualTo(originalDescription.toString());
+ assertThat(mTextView.getContentDescription().toString())
+ .isEqualTo(originalDescription.toString());
+ } catch (Throwable t) {
+ Helper.takeScreenshotAndSave(sContext, TestNameUtils.getCurrentTestName(),
+ Helper.LOCAL_TEST_FILES_DIR);
+ throw t;
+ }
}
@Test
@@ -587,68 +617,80 @@
@Test
public void testUiTranslation_translationResponseNotSetForCustomTextView() throws Throwable {
- // Enable CTS ContentCaptureService
- CtsContentCaptureService contentcaptureService = enableContentCaptureService();
- // Start Activity and get needed information
- final List<AutofillId> views = startCustomTextViewActivityAndGetViewsForTranslation();
+ try {
+ // Enable CTS ContentCaptureService
+ CtsContentCaptureService contentcaptureService = enableContentCaptureService();
+ // Start Activity and get needed information
+ final List<AutofillId> views = startCustomTextViewActivityAndGetViewsForTranslation();
- // Wait session created and get the ConttCaptureContext from ContentCaptureService
- final ContentCaptureContext contentCaptureContext =
- getContentCaptureContextFromContentCaptureService(contentcaptureService);
+ // Wait session created and get the ConttCaptureContext from ContentCaptureService
+ final ContentCaptureContext contentCaptureContext =
+ getContentCaptureContextFromContentCaptureService(contentcaptureService);
- // enable CTS TranslationService
- mTranslationServiceServiceWatcher = CtsTranslationService.setServiceWatcher();
- Helper.setTemporaryTranslationService(CtsTranslationService.SERVICE_NAME);
+ // enable CTS TranslationService
+ mTranslationServiceServiceWatcher = CtsTranslationService.setServiceWatcher();
+ Helper.setTemporaryTranslationService(CtsTranslationService.SERVICE_NAME);
- // Set response
- final TranslationResponse expectedResponse =
- createViewsTranslationResponse(views, "success");
- sTranslationReplier.addResponse(expectedResponse);
+ // Set response
+ final TranslationResponse expectedResponse =
+ createViewsTranslationResponse(views, "success");
+ sTranslationReplier.addResponse(expectedResponse);
- startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
- // Verify result. Translation response doesn't set, it should show original text
- assertThat(mResponseNotSetTextView.getSavedResponse()).isNotNull();
- final UiObject2 responseNotSetText = Helper.findObjectByResId(Helper.ACTIVITY_PACKAGE,
- CustomTextViewActivity.ID_RESPONSE_NOT_SET_TEXT);
- assertThat(responseNotSetText).isNotNull();
- assertThat(responseNotSetText.getText()).isEqualTo("Hello World 1");
+ // Verify result. Translation response doesn't set, it should show original text
+ assertThat(mResponseNotSetTextView.getSavedResponse()).isNotNull();
+ final UiObject2 responseNotSetText = Helper.findObjectByResId(Helper.ACTIVITY_PACKAGE,
+ CustomTextViewActivity.ID_RESPONSE_NOT_SET_TEXT);
+ assertThat(responseNotSetText).isNotNull();
+ assertThat(responseNotSetText.getText()).isEqualTo("Hello World 1");
+ } catch (Throwable t) {
+ Helper.takeScreenshotAndSave(sContext, TestNameUtils.getCurrentTestName(),
+ Helper.LOCAL_TEST_FILES_DIR);
+ throw t;
+ }
}
@Test
@FlakyTest(bugId = 192418800)
public void testUiTranslation_customTextView() throws Throwable {
- // Enable CTS ContentCaptureService
- CtsContentCaptureService contentcaptureService = enableContentCaptureService();
- // Start Activity and get needed information
- final List<AutofillId> views = startCustomTextViewActivityAndGetViewsForTranslation();
+ try {
+ // Enable CTS ContentCaptureService
+ CtsContentCaptureService contentcaptureService = enableContentCaptureService();
+ // Start Activity and get needed information
+ final List<AutofillId> views = startCustomTextViewActivityAndGetViewsForTranslation();
- // Wait session created and get the ConttCaptureContext from ContentCaptureService
- final ContentCaptureContext contentCaptureContext =
- getContentCaptureContextFromContentCaptureService(contentcaptureService);
+ // Wait session created and get the ConttCaptureContext from ContentCaptureService
+ final ContentCaptureContext contentCaptureContext =
+ getContentCaptureContextFromContentCaptureService(contentcaptureService);
- // enable CTS TranslationService
- mTranslationServiceServiceWatcher = CtsTranslationService.setServiceWatcher();
- Helper.setTemporaryTranslationService(CtsTranslationService.SERVICE_NAME);
+ // enable CTS TranslationService
+ mTranslationServiceServiceWatcher = CtsTranslationService.setServiceWatcher();
+ Helper.setTemporaryTranslationService(CtsTranslationService.SERVICE_NAME);
- final String translatedText = "success";
- // Set response
- final TranslationResponse expectedResponse =
- createViewsTranslationResponse(views, translatedText);
- sTranslationReplier.addResponse(expectedResponse);
+ final String translatedText = "success";
+ // Set response
+ final TranslationResponse expectedResponse =
+ createViewsTranslationResponse(views, translatedText);
+ sTranslationReplier.addResponse(expectedResponse);
- startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
- // Verify result.
- assertThat(mCustomTextView.isMyTagTranslationSupported()).isTrue();
- final UiObject2 customText = Helper.findObjectByResId(Helper.ACTIVITY_PACKAGE,
- CustomTextViewActivity.ID_CUSTOM_TEXT);
- assertThat(customText).isNotNull();
- assertThat(customText.getText()).isEqualTo(translatedText);
+ // Verify result.
+ assertThat(mCustomTextView.isMyTagTranslationSupported()).isTrue();
+ final UiObject2 customText = Helper.findObjectByResId(Helper.ACTIVITY_PACKAGE,
+ CustomTextViewActivity.ID_CUSTOM_TEXT);
+ assertThat(customText).isNotNull();
+ assertThat(customText.getText()).isEqualTo(translatedText);
- finishUiTranslation(contentCaptureContext);
+ finishUiTranslation(contentCaptureContext);
- assertThat(customText.getText()).isEqualTo("Hello World 2");
+ assertThat(customText.getText()).isEqualTo("Hello World 2");
+ } catch (Throwable t) {
+ Helper.takeScreenshotAndSave(sContext, TestNameUtils.getCurrentTestName(),
+ Helper.LOCAL_TEST_FILES_DIR);
+ throw t;
+ }
}
private void startUiTranslation(boolean shouldPadContent, List<AutofillId> views,
diff --git a/tests/uwb/Android.bp b/tests/uwb/Android.bp
index 7bcdc37..33b8441 100644
--- a/tests/uwb/Android.bp
+++ b/tests/uwb/Android.bp
@@ -18,11 +18,12 @@
android_test {
name: "CtsUwbTestCases",
- defaults: ["cts_defaults"],
+ defaults: ["cts_defaults", "framework-uwb-cts-defaults"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
+ "mts-uwb",
],
libs: ["android.test.runner"],
static_libs: [
diff --git a/tests/uwb/AndroidTest.xml b/tests/uwb/AndroidTest.xml
index f0bb20a..3104ec5 100644
--- a/tests/uwb/AndroidTest.xml
+++ b/tests/uwb/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.uwb.apex" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsUwbTestCases.apk" />
@@ -26,4 +27,7 @@
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.uwb.cts" />
</test>
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.uwb" />
+ </object>
</configuration>
diff --git a/tests/uwb/OWNERS b/tests/uwb/OWNERS
index 7ba57cf..c4ad416 100644
--- a/tests/uwb/OWNERS
+++ b/tests/uwb/OWNERS
@@ -1,5 +1,2 @@
-# Bug component: 898555
-bstack@google.com
-eliptus@google.com
-jsolnit@google.com
-zachoverflow@google.com
+# Bug component: 1042770
+include platform/packages/modules/Uwb:/OWNERS
diff --git a/tests/uwb/src/android/uwb/cts/RangingMeasurementTest.java b/tests/uwb/src/android/uwb/cts/RangingMeasurementTest.java
index d57a636..bc5c2fd 100644
--- a/tests/uwb/src/android/uwb/cts/RangingMeasurementTest.java
+++ b/tests/uwb/src/android/uwb/cts/RangingMeasurementTest.java
@@ -17,6 +17,7 @@
package android.uwb.cts;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.os.Parcel;
@@ -38,6 +39,8 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RangingMeasurementTest {
+ private static final int TEST_RSSI_DBM = -80;
+ private static final int INVALID_RSSI_DBM = -129;
@Test
public void testBuilder() {
@@ -45,7 +48,12 @@
UwbAddress address = UwbTestUtils.getUwbAddress(false);
long time = SystemClock.elapsedRealtimeNanos();
AngleOfArrivalMeasurement angleMeasurement = UwbTestUtils.getAngleOfArrivalMeasurement();
+ AngleOfArrivalMeasurement destinationAngleMeasurement =
+ UwbTestUtils.getAngleOfArrivalMeasurement();
DistanceMeasurement distanceMeasurement = UwbTestUtils.getDistanceMeasurement();
+ int los = RangingMeasurement.NLOS;
+ int measurementFocus = RangingMeasurement.MEASUREMENT_FOCUS_RANGE;
+
RangingMeasurement.Builder builder = new RangingMeasurement.Builder();
@@ -58,17 +66,45 @@
builder.setAngleOfArrivalMeasurement(angleMeasurement);
tryBuild(builder, false);
+ builder.setDestinationAngleOfArrivalMeasurement(destinationAngleMeasurement);
+ tryBuild(builder, false);
+
builder.setDistanceMeasurement(distanceMeasurement);
tryBuild(builder, false);
+ builder.setRssiDbm(TEST_RSSI_DBM);
+ tryBuild(builder, false);
+
builder.setRemoteDeviceAddress(address);
+ tryBuild(builder, true);
+
+ builder.setLineOfSight(los);
+ tryBuild(builder, true);
+
+ builder.setMeasurementFocus(measurementFocus);
RangingMeasurement measurement = tryBuild(builder, true);
assertEquals(status, measurement.getStatus());
assertEquals(address, measurement.getRemoteDeviceAddress());
assertEquals(time, measurement.getElapsedRealtimeNanos());
assertEquals(angleMeasurement, measurement.getAngleOfArrivalMeasurement());
+ assertEquals(destinationAngleMeasurement,
+ measurement.getDestinationAngleOfArrivalMeasurement());
assertEquals(distanceMeasurement, measurement.getDistanceMeasurement());
+ assertEquals(los, measurement.getLineOfSight());
+ assertEquals(measurementFocus, measurement.getMeasurementFocus());
+ assertEquals(TEST_RSSI_DBM, measurement.getRssiDbm());
+ }
+
+ @Test
+ public void testInvalidRssi() {
+ RangingMeasurement.Builder builder = new RangingMeasurement.Builder();
+ try {
+ builder.setRssiDbm(INVALID_RSSI_DBM);
+ fail("Expected RangingMeasurement.Builder.setRssiDbm() to fail");
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("Invalid"));
+ }
}
private RangingMeasurement tryBuild(RangingMeasurement.Builder builder,
diff --git a/tests/uwb/src/android/uwb/cts/RangingSessionTest.java b/tests/uwb/src/android/uwb/cts/RangingSessionTest.java
index b9725f1..c638876 100644
--- a/tests/uwb/src/android/uwb/cts/RangingSessionTest.java
+++ b/tests/uwb/src/android/uwb/cts/RangingSessionTest.java
@@ -16,6 +16,8 @@
package android.uwb.cts;
+import static android.uwb.RangingSession.Callback.REASON_BAD_PARAMETERS;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -30,10 +32,11 @@
import android.os.PersistableBundle;
import android.os.RemoteException;
-import android.uwb.IUwbAdapter;
+import android.uwb.IUwbAdapter2;
import android.uwb.RangingReport;
import android.uwb.RangingSession;
import android.uwb.SessionHandle;
+import android.uwb.UwbAddress;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -53,6 +56,7 @@
public class RangingSessionTest {
private static final Executor EXECUTOR = UwbTestUtils.getExecutor();
private static final PersistableBundle PARAMS = new PersistableBundle();
+ private static final UwbAddress UWB_ADDRESS = UwbAddress.fromBytes(new byte[] {0x00, 0x56});
private static final @RangingSession.Callback.Reason int REASON =
RangingSession.Callback.REASON_GENERIC_ERROR;
@@ -60,7 +64,7 @@
public void testOnRangingOpened_OnOpenSuccessCalled() {
SessionHandle handle = new SessionHandle(123);
RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
+ IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
verifyOpenState(session, false);
@@ -73,10 +77,33 @@
}
@Test
+ public void testOnRangingOpened_OnServiceDiscoveredConnectedCalled() {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+ verifyOpenState(session, false);
+
+ session.onRangingOpened();
+ verifyOpenState(session, true);
+
+ // Verify that the onOpenSuccess callback was invoked
+ verify(callback, times(1)).onOpened(eq(session));
+ verify(callback, times(0)).onClosed(anyInt(), any());
+
+ session.onServiceDiscovered(PARAMS);
+ verify(callback, times(1)).onServiceDiscovered(eq(PARAMS));
+
+ session.onServiceConnected(PARAMS);
+ verify(callback, times(1)).onServiceConnected(eq(PARAMS));
+ }
+
+
+ @Test
public void testOnRangingOpened_CannotOpenClosedSession() {
SessionHandle handle = new SessionHandle(123);
RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
+ IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
session.onRangingOpened();
@@ -100,7 +127,7 @@
public void testOnRangingClosed_OnClosedCalledWhenSessionNotOpen() {
SessionHandle handle = new SessionHandle(123);
RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
+ IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
verifyOpenState(session, false);
@@ -116,7 +143,7 @@
public void testOnRangingClosed_OnClosedCalled() {
SessionHandle handle = new SessionHandle(123);
RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
+ IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
session.onRangingStarted(PARAMS);
session.onRangingClosed(REASON, PARAMS);
@@ -131,7 +158,7 @@
public void testOnRangingResult_OnReportReceivedCalled() {
SessionHandle handle = new SessionHandle(123);
RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
+ IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
verifyOpenState(session, false);
@@ -147,7 +174,7 @@
public void testStart_CannotStartIfAlreadyStarted() throws RemoteException {
SessionHandle handle = new SessionHandle(123);
RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
+ IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
session.onRangingOpened();
@@ -164,7 +191,7 @@
public void testStop_CannotStopIfAlreadyStopped() throws RemoteException {
SessionHandle handle = new SessionHandle(123);
RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
+ IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
doAnswer(new StopAnswer(session)).when(adapter).stopRanging(any());
@@ -180,45 +207,137 @@
}
@Test
- public void testReconfigure_OnlyWhenOpened() throws RemoteException {
+ public void testCallbacks_OnlyWhenOpened() throws RemoteException {
SessionHandle handle = new SessionHandle(123);
RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
+ IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+ doAnswer(new OpenAnswer(session)).when(adapter).openRanging(
+ any(), any(), any(), any(), any());
doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
doAnswer(new ReconfigureAnswer(session)).when(adapter).reconfigureRanging(any(), any());
+ doAnswer(new SuspendAnswer(session)).when(adapter).suspend(any(), any());
+ doAnswer(new ResumeAnswer(session)).when(adapter).resume(any(), any());
+ doAnswer(new ControleeAddAnswer(session)).when(adapter).addControlee(any(), any());
+ doAnswer(new ControleeRemoveAnswer(session)).when(adapter).removeControlee(any(), any());
+ doAnswer(new DataSendAnswer(session)).when(adapter).sendData(any(), any(), any(), any());
+ doAnswer(new StopAnswer(session)).when(adapter).stopRanging(any());
verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
verify(callback, times(0)).onReconfigured(any());
verifyOpenState(session, false);
+ session.onRangingOpenFailed(REASON_BAD_PARAMETERS, PARAMS);
+ verifyOpenState(session, false);
+ verify(callback, times(1)).onOpenFailed(
+ REASON_BAD_PARAMETERS, PARAMS);
+
session.onRangingOpened();
+ verifyOpenState(session, true);
+ verify(callback, times(1)).onStarted(any());
verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
verify(callback, times(1)).onReconfigured(any());
+ verifyThrowIllegalState(() -> session.suspend(PARAMS));
+ verify(callback, times(0)).onSuspended(any());
+ verifyThrowIllegalState(() -> session.resume(PARAMS));
+ verify(callback, times(0)).onResumed(any());
+ verifyNoThrowIllegalState(() -> session.addControlee(PARAMS));
+ verify(callback, times(1)).onControleeAdded(any());
+ verifyNoThrowIllegalState(() -> session.removeControlee(PARAMS));
+ verify(callback, times(1)).onControleeRemoved(any());
+ verifyThrowIllegalState(() -> session.sendData(
+ UWB_ADDRESS, PARAMS, new byte[] {0x05, 0x1}));
+ verify(callback, times(0)).onDataSent(any(), any());
+
+ session.onRangingStartFailed(REASON_BAD_PARAMETERS, PARAMS);
verifyOpenState(session, true);
+ verify(callback, times(1)).onStartFailed(
+ REASON_BAD_PARAMETERS, PARAMS);
session.onRangingStarted(PARAMS);
+ verifyOpenState(session, true);
verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
verify(callback, times(2)).onReconfigured(any());
- verifyOpenState(session, true);
+ verifyNoThrowIllegalState(() -> session.reconfigure(null));
+ verify(callback, times(2)).onReconfigureFailed(
+ eq(REASON_BAD_PARAMETERS), any());
+ verifyNoThrowIllegalState(() -> session.suspend(PARAMS));
+ verify(callback, times(1)).onSuspended(any());
+ verifyNoThrowIllegalState(() -> session.suspend(null));
+ verify(callback, times(1)).onSuspendFailed(
+ eq(REASON_BAD_PARAMETERS), any());
+ verifyNoThrowIllegalState(() -> session.resume(PARAMS));
+ verify(callback, times(1)).onResumed(any());
+ verifyNoThrowIllegalState(() -> session.resume(null));
+ verify(callback, times(1)).onResumeFailed(
+ eq(REASON_BAD_PARAMETERS), any());
+ verifyNoThrowIllegalState(() -> session.addControlee(PARAMS));
+ verify(callback, times(2)).onControleeAdded(any());
+ verifyNoThrowIllegalState(() -> session.addControlee(null));
+ verify(callback, times(1)).onControleeAddFailed(
+ eq(REASON_BAD_PARAMETERS), any());
+ verifyNoThrowIllegalState(() -> session.removeControlee(PARAMS));
+ verify(callback, times(2)).onControleeRemoved(any());
+ verifyNoThrowIllegalState(() -> session.removeControlee(null));
+ verify(callback, times(1)).onControleeRemoveFailed(
+ eq(REASON_BAD_PARAMETERS), any());
+ verifyNoThrowIllegalState(() -> session.sendData(
+ UWB_ADDRESS, PARAMS, new byte[] {0x05, 0x1}));
+ verify(callback, times(1)).onDataSent(any(), any());
+ verifyNoThrowIllegalState(() -> session.sendData(
+ null, PARAMS, new byte[] {0x05, 0x1}));
+ verify(callback, times(1)).onDataSendFailed(
+ eq(null), eq(REASON_BAD_PARAMETERS), any());
- session.onRangingStopped(REASON, PARAMS);
+ session.onDataReceived(UWB_ADDRESS, PARAMS, new byte[] {0x5, 0x7});
+ verify(callback, times(1)).onDataReceived(
+ UWB_ADDRESS, PARAMS, new byte[] {0x5, 0x7});
+ session.onDataReceiveFailed(UWB_ADDRESS, REASON_BAD_PARAMETERS, PARAMS);
+ verify(callback, times(1)).onDataReceiveFailed(
+ UWB_ADDRESS, REASON_BAD_PARAMETERS, PARAMS);
+
+ session.stop();
+ verifyOpenState(session, true);
+ verify(callback, times(1)).onStopped(REASON, PARAMS);
+
verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
verify(callback, times(3)).onReconfigured(any());
- verifyOpenState(session, true);
+ verifyThrowIllegalState(() -> session.suspend(PARAMS));
+ verify(callback, times(1)).onSuspended(any());
+ verifyThrowIllegalState(() -> session.resume(PARAMS));
+ verify(callback, times(1)).onResumed(any());
+ verifyNoThrowIllegalState(() -> session.addControlee(PARAMS));
+ verify(callback, times(3)).onControleeAdded(any());
+ verifyNoThrowIllegalState(() -> session.removeControlee(PARAMS));
+ verify(callback, times(3)).onControleeRemoved(any());
+ verifyThrowIllegalState(() -> session.sendData(
+ UWB_ADDRESS, PARAMS, new byte[] {0x05, 0x1}));
+ verify(callback, times(1)).onDataSent(any(), any());
+ session.close();
+ verifyOpenState(session, false);
+ verify(callback, times(1)).onClosed(REASON, PARAMS);
- session.onRangingClosed(REASON, PARAMS);
verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
verify(callback, times(3)).onReconfigured(any());
- verifyOpenState(session, false);
+ verifyThrowIllegalState(() -> session.suspend(PARAMS));
+ verify(callback, times(1)).onSuspended(any());
+ verifyThrowIllegalState(() -> session.resume(PARAMS));
+ verify(callback, times(1)).onResumed(any());
+ verifyThrowIllegalState(() -> session.addControlee(PARAMS));
+ verify(callback, times(3)).onControleeAdded(any());
+ verifyThrowIllegalState(() -> session.removeControlee(PARAMS));
+ verify(callback, times(3)).onControleeRemoved(any());
+ verifyThrowIllegalState(() -> session.sendData(
+ UWB_ADDRESS, PARAMS, new byte[] {0x05, 0x1}));
+ verify(callback, times(1)).onDataSent(any(), any());
}
@Test
public void testClose_NoCallbackUntilInvoked() throws RemoteException {
SessionHandle handle = new SessionHandle(123);
RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
+ IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
session.onRangingOpened();
@@ -247,7 +366,7 @@
public void testClose_OnClosedCalled() throws RemoteException {
SessionHandle handle = new SessionHandle(123);
RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
+ IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any());
session.onRangingOpened();
@@ -260,7 +379,7 @@
public void testClose_CannotInteractFurther() throws RemoteException {
SessionHandle handle = new SessionHandle(123);
RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
+ IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any());
session.close();
@@ -275,7 +394,7 @@
public void testOnRangingResult_OnReportReceivedCalledWhenOpen() {
SessionHandle handle = new SessionHandle(123);
RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
+ IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
assertFalse(session.isOpen());
@@ -292,7 +411,7 @@
public void testOnRangingResult_OnReportReceivedNotCalledWhenNotOpen() {
SessionHandle handle = new SessionHandle(123);
RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
+ IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
assertFalse(session.isOpen());
@@ -332,6 +451,23 @@
}
}
+ class OpenAnswer extends AdapterAnswer {
+ OpenAnswer(RangingSession session) {
+ super(session);
+ }
+
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ PersistableBundle argParams = invocation.getArgument(1);
+ if (argParams != null) {
+ mSession.onRangingOpened();
+ } else {
+ mSession.onRangingOpenFailed(REASON_BAD_PARAMETERS, PARAMS);
+ }
+ return null;
+ }
+ }
+
class StartAnswer extends AdapterAnswer {
StartAnswer(RangingSession session) {
super(session);
@@ -339,7 +475,12 @@
@Override
public Object answer(InvocationOnMock invocation) {
- mSession.onRangingStarted(PARAMS);
+ PersistableBundle argParams = invocation.getArgument(1);
+ if (argParams != null) {
+ mSession.onRangingStarted(PARAMS);
+ } else {
+ mSession.onRangingStartFailed(REASON_BAD_PARAMETERS, PARAMS);
+ }
return null;
}
}
@@ -351,7 +492,97 @@
@Override
public Object answer(InvocationOnMock invocation) {
- mSession.onRangingReconfigured(PARAMS);
+ PersistableBundle argParams = invocation.getArgument(1);
+ if (argParams != null) {
+ mSession.onRangingReconfigured(PARAMS);
+ } else {
+ mSession.onRangingReconfigureFailed(REASON_BAD_PARAMETERS, PARAMS);
+ }
+ return null;
+ }
+ }
+
+ class SuspendAnswer extends AdapterAnswer {
+ SuspendAnswer(RangingSession session) {
+ super(session);
+ }
+
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ PersistableBundle argParams = invocation.getArgument(1);
+ if (argParams != null) {
+ mSession.onRangingSuspended(PARAMS);
+ } else {
+ mSession.onRangingSuspendFailed(REASON_BAD_PARAMETERS, PARAMS);
+ }
+ return null;
+ }
+ }
+
+ class ResumeAnswer extends AdapterAnswer {
+ ResumeAnswer(RangingSession session) {
+ super(session);
+ }
+
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ PersistableBundle argParams = invocation.getArgument(1);
+ if (argParams != null) {
+ mSession.onRangingResumed(PARAMS);
+ } else {
+ mSession.onRangingResumeFailed(REASON_BAD_PARAMETERS, PARAMS);
+ }
+ return null;
+ }
+ }
+
+ class ControleeAddAnswer extends AdapterAnswer {
+ ControleeAddAnswer(RangingSession session) {
+ super(session);
+ }
+
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ PersistableBundle argParams = invocation.getArgument(1);
+ if (argParams != null) {
+ mSession.onControleeAdded(PARAMS);
+ } else {
+ mSession.onControleeAddFailed(REASON_BAD_PARAMETERS, PARAMS);
+ }
+ return null;
+ }
+ }
+
+ class ControleeRemoveAnswer extends AdapterAnswer {
+ ControleeRemoveAnswer(RangingSession session) {
+ super(session);
+ }
+
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ PersistableBundle argParams = invocation.getArgument(1);
+ if (argParams != null) {
+ mSession.onControleeRemoved(PARAMS);
+ } else {
+ mSession.onControleeRemoveFailed(REASON_BAD_PARAMETERS, PARAMS);
+ }
+ return null;
+ }
+ }
+
+ class DataSendAnswer extends AdapterAnswer {
+ DataSendAnswer(RangingSession session) {
+ super(session);
+ }
+
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ UwbAddress argParams = invocation.getArgument(1);
+ if (argParams != null) {
+ mSession.onDataSent(UWB_ADDRESS, PARAMS);
+ } else {
+ mSession.onDataSendFailed(null, REASON_BAD_PARAMETERS, PARAMS);
+ }
return null;
}
}
diff --git a/tests/uwb/src/android/uwb/cts/UwbManagerTest.java b/tests/uwb/src/android/uwb/cts/UwbManagerTest.java
index a887709..0d85c03 100644
--- a/tests/uwb/src/android/uwb/cts/UwbManagerTest.java
+++ b/tests/uwb/src/android/uwb/cts/UwbManagerTest.java
@@ -18,11 +18,14 @@
import static android.Manifest.permission.UWB_PRIVILEGED;
import static android.Manifest.permission.UWB_RANGING;
+import static android.uwb.UwbManager.AdapterStateCallback.STATE_DISABLED;
+import static android.uwb.UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@@ -46,10 +49,13 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
@@ -66,12 +72,43 @@
private final Context mContext = InstrumentationRegistry.getContext();
private UwbManager mUwbManager;
+ private String mDefaultChipId;
@Before
- public void setup() {
+ public void setup() throws Exception {
mUwbManager = mContext.getSystemService(UwbManager.class);
assumeTrue(UwbTestUtils.isUwbSupported(mContext));
assertThat(mUwbManager).isNotNull();
+
+ // Ensure UWB is toggled on.
+ ShellIdentityUtils.invokeWithShellPermissions(() -> {
+ if (!mUwbManager.isUwbEnabled()) {
+ try {
+ setUwbEnabledAndWaitForCompletion(true);
+ } catch (Exception e) {
+ fail("Exception while processing UWB toggle " + e);
+ }
+ }
+ mDefaultChipId = mUwbManager.getDefaultChipId();
+ });
+ }
+
+ // Should be invoked with shell permissions.
+ private void setUwbEnabledAndWaitForCompletion(boolean enabled) throws Exception {
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ int adapterState = enabled ? STATE_ENABLED_INACTIVE : STATE_DISABLED;
+ AdapterStateCallback adapterStateCallback =
+ new AdapterStateCallback(countDownLatch, adapterState);
+ try {
+ mUwbManager.registerAdapterStateCallback(
+ Executors.newSingleThreadExecutor(), adapterStateCallback);
+ mUwbManager.setUwbEnabled(enabled);
+ assertThat(countDownLatch.await(2, TimeUnit.SECONDS)).isTrue();
+ assertThat(mUwbManager.isUwbEnabled()).isEqualTo(enabled);
+ assertThat(adapterStateCallback.state).isEqualTo(adapterState);
+ } finally {
+ mUwbManager.unregisterAdapterStateCallback(adapterStateCallback);
+ }
}
@Test
@@ -89,6 +126,48 @@
}
@Test
+ public void testGetSpecificationInfoWithChipId() {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ try {
+ // Needs UWB_PRIVILEGED permission which is held by shell.
+ uiAutomation.adoptShellPermissionIdentity();
+ PersistableBundle persistableBundle =
+ mUwbManager.getSpecificationInfo(mDefaultChipId);
+ assertThat(persistableBundle).isNotNull();
+ assertThat(persistableBundle.isEmpty()).isFalse();
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
+ public void testGetChipIds() {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ try {
+ // Needs UWB_PRIVILEGED permission which is held by shell.
+ uiAutomation.adoptShellPermissionIdentity();
+ List<String> chipIds = mUwbManager.getChipIds();
+ assertThat(chipIds).isNotEmpty();
+ assertThat(chipIds).contains(mDefaultChipId);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
+ public void testGetSpecificationInfoWithInvalidChipId() {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ try {
+ // Needs UWB_PRIVILEGED permission which is held by shell.
+ uiAutomation.adoptShellPermissionIdentity();
+ assertThrows(IllegalArgumentException.class,
+ () -> mUwbManager.getSpecificationInfo("invalidChipId"));
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
public void testGetSpecificationInfoWithoutUwbPrivileged() {
try {
mUwbManager.getSpecificationInfo();
@@ -100,6 +179,18 @@
}
}
+ @Test
+ public void testGetSpecificationInfoWithChipIdWithoutUwbPrivileged() {
+ try {
+ mUwbManager.getSpecificationInfo(mDefaultChipId);
+ // should fail if the call was successful without UWB_PRIVILEGED permission.
+ fail();
+ } catch (SecurityException e) {
+ /* pass */
+ Log.i(TAG, "Failed with expected security exception: " + e);
+ }
+ }
+
@Test
public void testElapsedRealtimeResolutionNanos() {
@@ -114,6 +205,32 @@
}
@Test
+ public void testElapsedRealtimeResolutionNanosWithChipId() {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ try {
+ // Needs UWB_PRIVILEGED permission which is held by shell.
+ uiAutomation.adoptShellPermissionIdentity();
+ assertThat(mUwbManager.elapsedRealtimeResolutionNanos(mDefaultChipId) >= 0L)
+ .isTrue();
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
+ public void testElapsedRealtimeResolutionNanosWithInvalidChipId() {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ try {
+ // Needs UWB_PRIVILEGED permission which is held by shell.
+ uiAutomation.adoptShellPermissionIdentity();
+ assertThrows(IllegalArgumentException.class,
+ () -> mUwbManager.elapsedRealtimeResolutionNanos("invalidChipId"));
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
public void testElapsedRealtimeResolutionNanosWithoutUwbPrivileged() {
try {
mUwbManager.elapsedRealtimeResolutionNanos();
@@ -125,6 +242,144 @@
}
}
+ @Test
+ public void testElapsedRealtimeResolutionNanosWithChipIdWithoutUwbPrivileged() {
+ try {
+ mUwbManager.elapsedRealtimeResolutionNanos(mDefaultChipId);
+ // should fail if the call was successful without UWB_PRIVILEGED permission.
+ fail();
+ } catch (SecurityException e) {
+ /* pass */
+ Log.i(TAG, "Failed with expected security exception: " + e);
+ }
+ }
+
+ @Test
+ public void testAddServiceProfileWithoutUwbPrivileged() {
+ try {
+ mUwbManager.addServiceProfile(new PersistableBundle());
+ // should fail if the call was successful without UWB_PRIVILEGED permission.
+ fail();
+ } catch (SecurityException e) {
+ /* pass */
+ Log.i(TAG, "Failed with expected security exception: " + e);
+ }
+ }
+
+ @Test
+ public void testRemoveServiceProfileWithoutUwbPrivileged() {
+ try {
+ mUwbManager.removeServiceProfile(new PersistableBundle());
+ // should fail if the call was successful without UWB_PRIVILEGED permission.
+ fail();
+ } catch (SecurityException e) {
+ /* pass */
+ Log.i(TAG, "Failed with expected security exception: " + e);
+ }
+ }
+
+
+ @Test
+ public void testGetAllServiceProfilesWithoutUwbPrivileged() {
+ try {
+ mUwbManager.getAllServiceProfiles();
+ // should fail if the call was successful without UWB_PRIVILEGED permission.
+ fail();
+ } catch (SecurityException e) {
+ /* pass */
+ Log.i(TAG, "Failed with expected security exception: " + e);
+ }
+ }
+
+ @Test
+ public void testGetAdfProvisioningAuthoritiesWithoutUwbPrivileged() {
+ try {
+ mUwbManager.getAdfProvisioningAuthorities(new PersistableBundle());
+ // should fail if the call was successful without UWB_PRIVILEGED permission.
+ fail();
+ } catch (SecurityException e) {
+ /* pass */
+ Log.i(TAG, "Failed with expected security exception: " + e);
+ }
+ }
+
+ @Test
+ public void testGetAdfCertificateInfoWithoutUwbPrivileged() {
+ try {
+ mUwbManager.getAdfCertificateInfo(new PersistableBundle());
+ // should fail if the call was successful without UWB_PRIVILEGED permission.
+ fail();
+ } catch (SecurityException e) {
+ /* pass */
+ Log.i(TAG, "Failed with expected security exception: " + e);
+ }
+ }
+
+ @Test
+ public void testChipIdsWithoutUwbPrivileged() {
+ try {
+ mUwbManager.getChipIds();
+ // should fail if the call was successful without UWB_PRIVILEGED permission.
+ fail();
+ } catch (SecurityException e) {
+ /* pass */
+ Log.i(TAG, "Failed with expected security exception: " + e);
+ }
+ }
+
+ private class AdfProvisionStateCallback extends UwbManager.AdfProvisionStateCallback {
+ private final CountDownLatch mCountDownLatch;
+
+ public boolean onSuccessCalled;
+ public boolean onFailedCalled;
+
+ AdfProvisionStateCallback(@NonNull CountDownLatch countDownLatch) {
+ mCountDownLatch = countDownLatch;
+ }
+
+ @Override
+ public void onProfileAdfsProvisioned(@NonNull PersistableBundle params) {
+ onSuccessCalled = true;
+ mCountDownLatch.countDown();
+ }
+
+ @Override
+ public void onProfileAdfsProvisionFailed(int reason, @NonNull PersistableBundle params) {
+ onFailedCalled = true;
+ mCountDownLatch.countDown();
+ }
+ }
+
+ @Test
+ public void testProvisionProfileAdfByScriptWithoutUwbPrivileged() {
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ AdfProvisionStateCallback adfProvisionStateCallback =
+ new AdfProvisionStateCallback(countDownLatch);
+ try {
+ mUwbManager.provisionProfileAdfByScript(
+ new PersistableBundle(),
+ Executors.newSingleThreadExecutor(),
+ adfProvisionStateCallback);
+ // should fail if the call was successful without UWB_PRIVILEGED permission.
+ fail();
+ } catch (SecurityException e) {
+ /* pass */
+ Log.i(TAG, "Failed with expected security exception: " + e);
+ }
+ }
+
+ @Test
+ public void testRemoveProfileAdfWithoutUwbPrivileged() {
+ try {
+ mUwbManager.removeProfileAdf(new PersistableBundle());
+ // should fail if the call was successful without UWB_PRIVILEGED permission.
+ fail();
+ } catch (SecurityException e) {
+ /* pass */
+ Log.i(TAG, "Failed with expected security exception: " + e);
+ }
+ }
+
private class RangingSessionCallback implements RangingSession.Callback {
private final CountDownLatch mCountDownLatch;
@@ -165,7 +420,26 @@
}
@Test
- public void testOpenRangingSessionWithBadParams() throws Exception {
+ public void testOpenRangingSessionWithInvalidChipId() {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ RangingSessionCallback rangingSessionCallback = new RangingSessionCallback(countDownLatch);
+ try {
+ // Needs UWB_PRIVILEGED & UWB_RANGING permission which is held by shell.
+ uiAutomation.adoptShellPermissionIdentity();
+ // Try to start a ranging session with invalid params, should fail.
+ assertThrows(IllegalArgumentException.class, () -> mUwbManager.openRangingSession(
+ new PersistableBundle(),
+ Executors.newSingleThreadExecutor(),
+ rangingSessionCallback,
+ "invalidChipId"));
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
+ public void testOpenRangingSessionWithChipIdWithBadParams() throws Exception {
UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
CancellationSignal cancellationSignal = null;
CountDownLatch countDownLatch = new CountDownLatch(1);
@@ -177,7 +451,35 @@
cancellationSignal = mUwbManager.openRangingSession(
new PersistableBundle(),
Executors.newSingleThreadExecutor(),
- rangingSessionCallback);
+ rangingSessionCallback,
+ mDefaultChipId);
+ // Wait for the on start failed callback.
+ assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue();
+ assertThat(rangingSessionCallback.onOpenedCalled).isFalse();
+ assertThat(rangingSessionCallback.onOpenFailedCalled).isTrue();
+ } finally {
+ if (cancellationSignal != null) {
+ cancellationSignal.cancel();
+ }
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
+ public void testOpenRangingSessionWithInvalidChipIdWithBadParams() throws Exception {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ CancellationSignal cancellationSignal = null;
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ RangingSessionCallback rangingSessionCallback = new RangingSessionCallback(countDownLatch);
+ try {
+ // Needs UWB_PRIVILEGED & UWB_RANGING permission which is held by shell.
+ uiAutomation.adoptShellPermissionIdentity();
+ // Try to start a ranging session with invalid params, should fail.
+ cancellationSignal = mUwbManager.openRangingSession(
+ new PersistableBundle(),
+ Executors.newSingleThreadExecutor(),
+ rangingSessionCallback,
+ mDefaultChipId);
// Wait for the on start failed callback.
assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue();
assertThat(rangingSessionCallback.onOpenedCalled).isFalse();
@@ -212,6 +514,26 @@
}
}
+ @Test
+ public void testOpenRangingSessionWithChipIdWithoutUwbPrivileged() {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ try {
+ // Only hold UWB_RANGING permission
+ uiAutomation.adoptShellPermissionIdentity(UWB_RANGING);
+ mUwbManager.openRangingSession(new PersistableBundle(),
+ Executors.newSingleThreadExecutor(),
+ new RangingSessionCallback(new CountDownLatch(1)),
+ mDefaultChipId);
+ // should fail if the call was successful without UWB_PRIVILEGED permission.
+ fail();
+ } catch (SecurityException e) {
+ /* pass */
+ Log.i(TAG, "Failed with expected security exception: " + e);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
/**
* Simulates the app holding UWB_PRIVILEGED permission, but not UWB_RANGING.
*/
@@ -234,6 +556,26 @@
}
}
+ @Test
+ public void testOpenRangingSessionWithChipIdWithoutUwbRanging() {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ try {
+ // Needs UWB_PRIVILEGED permission which is held by shell.
+ uiAutomation.adoptShellPermissionIdentity(UWB_PRIVILEGED);
+ mUwbManager.openRangingSession(new PersistableBundle(),
+ Executors.newSingleThreadExecutor(),
+ new RangingSessionCallback(new CountDownLatch(1)),
+ mDefaultChipId);
+ // should fail if the call was successful without UWB_RANGING permission.
+ fail();
+ } catch (SecurityException e) {
+ /* pass */
+ Log.i(TAG, "Failed with expected security exception: " + e);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
private AttributionSource getShellAttributionSourceWithRenouncedPermissions(
@Nullable Set<String> renouncedPermissions) {
try {
@@ -288,4 +630,70 @@
uiAutomation.dropShellPermissionIdentity();
}
}
+
+ @Test
+ public void testOpenRangingSessionWithChipIdWithoutUwbRangingInNextAttributeSource() {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ try {
+ // Only hold UWB_PRIVILEGED permission
+ uiAutomation.adoptShellPermissionIdentity();
+ Context shellContextWithUwbRangingRenounced =
+ createShellContextWithRenouncedPermissionsAndAttributionSource(
+ Set.of(UWB_RANGING));
+ UwbManager uwbManagerWithUwbRangingRenounced =
+ shellContextWithUwbRangingRenounced.getSystemService(UwbManager.class);
+ uwbManagerWithUwbRangingRenounced.openRangingSession(new PersistableBundle(),
+ Executors.newSingleThreadExecutor(),
+ new RangingSessionCallback(new CountDownLatch(1)),
+ mDefaultChipId);
+ // should fail if the call was successful without UWB_RANGING permission.
+ fail();
+ } catch (SecurityException e) {
+ /* pass */
+ Log.i(TAG, "Failed with expected security exception: " + e);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ private class AdapterStateCallback implements UwbManager.AdapterStateCallback {
+ private final CountDownLatch mCountDownLatch;
+ private final @State Integer mWaitForState;
+ public int state;
+ public int reason;
+
+ AdapterStateCallback(@NonNull CountDownLatch countDownLatch,
+ @Nullable @State Integer waitForState) {
+ mCountDownLatch = countDownLatch;
+ mWaitForState = waitForState;
+ }
+
+ public void onStateChanged(@State int state, @StateChangedReason int reason) {
+ this.state = state;
+ this.reason = reason;
+ if (mWaitForState != null) {
+ if (mWaitForState == state) {
+ mCountDownLatch.countDown();
+ }
+ } else {
+ mCountDownLatch.countDown();
+ }
+ }
+ }
+
+ @Test
+ public void testUwbStateToggle() throws Exception {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ try {
+ // Needs UWB_PRIVILEGED permission which is held by shell.
+ uiAutomation.adoptShellPermissionIdentity();
+ assertThat(mUwbManager.isUwbEnabled()).isTrue();
+ assertThat(mUwbManager.getAdapterState()).isEqualTo(STATE_ENABLED_INACTIVE);
+ // Toggle the state
+ setUwbEnabledAndWaitForCompletion(false);
+ assertThat(mUwbManager.getAdapterState()).isEqualTo(STATE_DISABLED);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
}
diff --git a/tests/uwb/src/android/uwb/cts/UwbTestUtils.java b/tests/uwb/src/android/uwb/cts/UwbTestUtils.java
index 3790b52..041b66c 100644
--- a/tests/uwb/src/android/uwb/cts/UwbTestUtils.java
+++ b/tests/uwb/src/android/uwb/cts/UwbTestUtils.java
@@ -67,9 +67,13 @@
return new RangingMeasurement.Builder()
.setDistanceMeasurement(getDistanceMeasurement())
.setAngleOfArrivalMeasurement(getAngleOfArrivalMeasurement())
+ .setDestinationAngleOfArrivalMeasurement(getAngleOfArrivalMeasurement())
.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos())
.setRemoteDeviceAddress(address != null ? address : getUwbAddress(false))
.setStatus(RangingMeasurement.RANGING_STATUS_SUCCESS)
+ .setLineOfSight(RangingMeasurement.NLOS)
+ .setMeasurementFocus(RangingMeasurement.MEASUREMENT_FOCUS_RANGE)
+ .setRssiDbm(-85)
.build();
}
diff --git a/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java b/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
index 4a12d70..791843e 100644
--- a/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
+++ b/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
@@ -637,6 +637,8 @@
charsKeyNames.add(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE.getName());
charsKeyNames.add(CameraCharacteristics.EDGE_AVAILABLE_EDGE_MODES.getName());
charsKeyNames.add(CameraCharacteristics.FLASH_INFO_AVAILABLE.getName());
+ charsKeyNames.add(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL.getName());
+ charsKeyNames.add(CameraCharacteristics.FLASH_INFO_STRENGTH_DEFAULT_LEVEL.getName());
charsKeyNames.add(CameraCharacteristics.HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES.getName());
charsKeyNames.add(CameraCharacteristics.JPEG_AVAILABLE_THUMBNAIL_SIZES.getName());
charsKeyNames.add(CameraCharacteristics.LENS_FACING.getName());
@@ -658,6 +660,8 @@
charsKeyNames.add(CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH.getName());
charsKeyNames.add(CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT.getName());
charsKeyNames.add(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES.getName());
+ charsKeyNames.add(CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES.getName());
+ charsKeyNames.add(CameraCharacteristics.REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE.getName());
charsKeyNames.add(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM.getName());
charsKeyNames.add(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP.getName());
charsKeyNames.add(CameraCharacteristics.SCALER_CROPPING_TYPE.getName());
@@ -668,6 +672,7 @@
charsKeyNames.add(CameraCharacteristics.SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP.getName());
charsKeyNames.add(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION.getName());
charsKeyNames.add(CameraCharacteristics.SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS.getName());
+ charsKeyNames.add(CameraCharacteristics.SCALER_MANDATORY_TEN_BIT_OUTPUT_STREAM_COMBINATIONS.getName());
charsKeyNames.add(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1.getName());
charsKeyNames.add(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2.getName());
charsKeyNames.add(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1.getName());
diff --git a/tools/cts-tradefed/res/config/cts-known-failures.xml b/tools/cts-tradefed/res/config/cts-known-failures.xml
index df18066..72ff5ce 100644
--- a/tools/cts-tradefed/res/config/cts-known-failures.xml
+++ b/tools/cts-tradefed/res/config/cts-known-failures.xml
@@ -255,7 +255,4 @@
<option name="compatibility:exclude-filter" value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.MixedManagedProfileOwnerTest#testSetCameraDisabledLogged" />
<option name="compatibility:exclude-filter" value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.MixedProfileOwnerTest#testSetCameraDisabledLogged" />
- <!-- b/204721335 -->
- <option name="compatibility:exclude-filter" value="CtsWindowManagerJetpackTestCases android.server.wm.jetpack.SidecarTest#testSidecarInterface_onWindowLayoutChangeListener" />
-
</configuration>
diff --git a/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml b/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
index 9ddd8b1..f4da75b 100644
--- a/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
+++ b/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
@@ -77,8 +77,8 @@
<option name="compatibility:module-arg" value="CtsDeqpTestCases:include-filter:dEQP-GLES3.functional.prerequisite#*" />
<option name="compatibility:module-arg" value="CtsDeqpTestCases:include-filter:dEQP-VK.api.smoke#*" />
- <!-- b/183659262 Remove CtsPreferenceTestCases from cts-on-gsi -->
- <option name="compatibility:exclude-filter" value="CtsPreferenceTestCases" />
+ <!-- b/183636777 Remove CtsShortcutManagerPackage4 from cts-on-gsi -->
+ <option name="compatibility:exclude-filter" value="CtsShortcutManagerPackage4" />
<!-- b/185451791. Can't have single overlay package for both AOSP version and Google-signed mainline modules -->
<option name="compatibility:exclude-filter" value="CtsWifiTestCases android.net.wifi.cts.ConcurrencyTest#testPersistentGroupOperation" />
diff --git a/tools/cts-tradefed/res/config/cts-sim-include.xml b/tools/cts-tradefed/res/config/cts-sim-include.xml
index 656c008..522c8761 100644
--- a/tools/cts-tradefed/res/config/cts-sim-include.xml
+++ b/tools/cts-tradefed/res/config/cts-sim-include.xml
@@ -40,7 +40,6 @@
<option name="compatibility:include-filter" value="CtsTelephonyTestCases" />
<option name="compatibility:include-filter" value="CtsTelephony2TestCases" />
<option name="compatibility:include-filter" value="CtsTelephony3TestCases" />
- <option name="compatibility:include-filter" value="CtsTelephonySdk28TestCases" />
<option name="compatibility:include-filter" value="CtsTetheringTest" />
<option name="compatibility:include-filter" value="CtsUsageStatsTestCases" />
<option name="compatibility:include-filter" value="CtsVcnTestCases" />